From 61f3a8dd5b94d0a2bd99109d28f98790478ea6bc Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 8 Jun 2021 15:56:07 +0700 Subject: [PATCH 01/81] feat(web): Initial try for addLanguageKeyboards to return a Promise --- web/source/keyboards/kmwkeyboards.ts | 59 +++++++++++++++-------- web/source/kmwbase.ts | 8 ++-- web/testing/commonHeader.js | 71 +++++++++++++++------------- 3 files changed, 82 insertions(+), 56 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 5abd9f08a6..decfde4c0e 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1,4 +1,6 @@ /// +// Includes a promise polyfill (needed for IE) +/// namespace com.keyman.keyboards { class CloudRequestEntry { @@ -85,15 +87,14 @@ namespace com.keyman.keyboards { // Represents unspecified errors that occur when registering the results of a successful API call. const CLOUD_STUB_REGISTRATION_ERR = "The Cloud API failed to find an appropriate keyboard."; // Represents custom, specified KMW errors that occur when registering the results of a successful API call. - const CLOUD_REGISTRATION_ERROR = "Error occurred while registering keyboards: "; + const CLOUD_REGISTRATION_ERR = "Error occurred while registering keyboards: "; const MISSING_KEYBOARD = function(kbdid: string) { return kbdid + ' keyboard not found.'; } - interface RegistrationPromiseTuple { - // TODO (#5044): the parameter for `resolve` should match the type of the Promise in keymanCloudRequest. - resolve: () => void; + export interface RegistrationPromiseTuple { + resolve: (stubs: KeyboardStub[]) => void; reject: (err: Error) => void; } @@ -1064,14 +1065,14 @@ namespace com.keyman.keyboards { * * @param {Object} x metadata object **/ - register(x) { + register(x): KeyboardStub[] { const promiseid = x['timerid']; - let result: Error; + let result: KeyboardStub[] | Error; try { result = this._registerCore(x); } catch(err) { - result = new Error(CLOUD_REGISTRATION_ERROR + err); + result = new Error(CLOUD_REGISTRATION_ERR + err); } if(promiseid) { @@ -1081,13 +1082,15 @@ namespace com.keyman.keyboards { try { if(result instanceof Error) { promiseFuncs.reject(result as Error); - } else { // TODO (#5044): pass `result` to the `resolve` function. - promiseFuncs.resolve(); + } else if (promiseFuncs) { + promiseFuncs.resolve(result as KeyboardStub[]); } } finally { delete this.registrationResolvers[promiseid]; } } + + return result as KeyboardStub[]; } /** @@ -1095,8 +1098,9 @@ namespace com.keyman.keyboards { * * @param {Object} x metadata object **/ - private _registerCore(x): Error { // TODO (#5044): should return heterogenous type; allow array of stubs. + private _registerCore(x): KeyboardStub[] | Error { // TODO (#5044): should return heterogenous type; allow array of stubs. var options=x['options']; + let currentKeyboardStubsCount = this.keyboardStubs.length; // Indicate if unable to register keyboard if(typeof(x['error']) == 'string') { @@ -1135,7 +1139,9 @@ namespace com.keyman.keyboards { this.languagesPending = []; } - return null; // TODO (#5044): return the list of newly-registered stubs here. + //console.log(currentKeyboardStubsCount); + //console.log(this.keyboardStubs.slice(currentKeyboardStubsCount)); + return this.keyboardStubs.slice(currentKeyboardStubsCount); } /** @@ -1143,7 +1149,7 @@ namespace com.keyman.keyboards { * * @param {Object} languages Array of language names **/ - addLanguageKeyboards(languages) { + addLanguageKeyboards(languages: any): Promise { var i, j, lgName, cmd, first, addAll; // Defer registering keyboards by language until the language list has been loaded @@ -1183,12 +1189,17 @@ namespace com.keyman.keyboards { } if(cmd == '') { - this.keymanweb.util.alert('No keyboards are available for '+languages[0]+'. ' - +'Does it have another language name?'); + // TODO: Check useAlert flag to determine whether or not KeymanWeb should display its own alert messages + let msg = 'No keyboards are available for '+languages[0]+'. ' + +'Does it have another language name?'; + this.keymanweb.util.alert(msg); + return Promise.reject(new Error(msg)); } else { - this.keymanCloudRequest('&keyboardid='+cmd,false); + return this.keymanCloudRequest('&keyboardid='+cmd, false); } } + + // Can we get here? What do we return? } /** @@ -1197,9 +1208,10 @@ namespace com.keyman.keyboards { * @param {string} cmd command string * @param {boolean?} byLanguage if true, context=languages, else context=keyboards **/ - keymanCloudRequest(cmd: string, byLanguage?: boolean) { + keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise { var kbdManager = this; var keymanweb = this.keymanweb; + let currentKeyboardStubsCount = this.keyboardStubs.length; // Some basic support toward #5044, but definitely not a full solution toward it. // Wraps the cloud API keyboard-stub request in a Promise, allowing response on network @@ -1208,7 +1220,7 @@ namespace com.keyman.keyboards { const URL='https://api.keyman.com/cloud/4.0/' + ((arguments.length > 1) && byLanguage ? 'languages' : 'keyboards'); - let promise = new Promise(function(resolve: () => void, reject: (Error?) => void) { + let promise = new Promise(function(resolve: (result: KeyboardStub[]) => void, reject: (Error?) => void) { const Lscript: HTMLScriptElement = keymanweb.util._CreateElement('script'); const queryConfig = '?jsonp=keyman.register&languageidtype=bcp47&version='+keymanweb['version']; @@ -1216,7 +1228,7 @@ namespace com.keyman.keyboards { // Set callback timer const timeoutID = window.setTimeout(function() { delete kbdManager.registrationResolvers[timeoutID]; - reject(CLOUD_TIMEOUT_ERR); + reject(new Error(CLOUD_TIMEOUT_ERR)); } ,10000); // Save the resolve / reject functions. @@ -1268,14 +1280,21 @@ namespace com.keyman.keyboards { } catch(ex) { document.getElementsByTagName('head')[0].appendChild(Lscript); } - }); + // resolve? + //console.log(Lscript); + // why is this empty + //resolve(kbdManager.keyboardStubs.slice(currentKeyboardStubsCount)); + }); // TODO: Allow the site developer to handle error messaging via this catch. // This current version simply maintains pre-existing behavior. promise.catch(function(error: Error) { kbdManager.serverUnavailable(error); - throw error; + return Promise.reject(error); }); + + // Return new keyboard stubs. If out of bounds (no new stubs), returns [] + return Promise.resolve(this.keyboardStubs.slice(currentKeyboardStubsCount)); } /** diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index 08e35c3d19..e117991bdb 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -307,8 +307,8 @@ namespace com.keyman { * * @param {string} arg Language name (multiple arguments allowed) **/ - ['addKeyboardsForLanguage'](arg) { - this.keyboardManager.addLanguageKeyboards(arguments); + ['addKeyboardsForLanguage'](arg) : Promise { + return this.keyboardManager.addLanguageKeyboards(arguments); } /** @@ -316,8 +316,8 @@ namespace com.keyman { * * @param {Object} x metadata object **/ - ['register'](x) { - this.keyboardManager.register(x); + ['register'](x) : com.keyman.keyboards.KeyboardStub[] { + return this.keyboardManager.register(x); } /** diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index 8bdc835ca0..d8948655c2 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -1,47 +1,47 @@ // JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages -/* +/* The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available. If the same keyboard is used for several languages, it must be listed for each - language, but the keyboard itself will only be loaded once. + language, but the keyboard itself will only be loaded once. If two (or more) keyboards are to be available for a given language, both must be listed. - Any number of keyboards may be specified in one or more calls. - Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. The actual keyboard object will be downloaded asynchronously when first selected for use. - + Each argument to addKeyboards() is a string, for example: european2 loads the current version of the Eurolatin 2 keyboard (for its default language) european2@fr loads the current version of the Eurolatin 2 keyboard for French european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French - + Argument syntax also supports the following extensions: @fr load the current version of the default keyboard for French @fr$ load all available keyboards (current version) for French - - Each call to addKeyboards() requires a single call to the remote server, + + Each call to addKeyboards() requires a single call to the remote server, (unless all keyboards listed are local and fully specified) so it is better - to use multiple arguments rather than separate function calls. - - Calling addKeyboards() with no arguments returns a list of *all* available keyboards. - The Toolbar (desktop browser) UI is best suited for allowing users to select + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select the appropriate language and keyboard in this case. Keyboards may also be specified by language name using addKeyboardsForLanguage() for example: keymanweb.addKeyboardsForLanguage('Burmese'); - + Appending $ to the language name will again cause all available keyboards for that language to be loaded rather than the default keyboard. - - The first call to addKeyboardsForLanguage() makes an additional call to the + + The first call to addKeyboardsForLanguage() makes an additional call to the keyman API to load the current list of keyboard/language associations. In this example, the following function loads the indicated keyboards, - and is called when the page loads. + and is called when the page loads. */ function loadKeyboards(nestLevel) - { + { var kmw=keyman; var base_prefix = '../'; @@ -53,46 +53,46 @@ prefix = prefix + base_prefix; } } - + // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:(prefix + 'us-1.0.js')}); - + // Add more keyboards to the language menu, by keyboard name, // keyboard name and language code, or just the BCP-47 language code. // We use a different loading pattern here than in the samples version to provide a slightly different set of test cases. kmw.addKeyboards('french','@he'); kmw.addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string. - + // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) kmw.addKeyboardsForLanguage('Dzongkha'); - - // Add a fully-specified, locally-sourced, keyboard with custom font + + // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', languages: { id:'lo',name:'Lao',region:'Asia', }, filename:(prefix + 'lao_2008_basic-1.2.js') - }); + }); // The following two optional calls should be delayed until language menus are fully loaded: // (a) a specific mapped input element input is focused, to ensure that the OSK appears - // (b) a specific keyboard is loaded, rather than the keyboard last used. + // (b) a specific keyboard is loaded, rather than the keyboard last used. //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); - - // Note that locally specified keyboards will be listed before keyboards + + // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order // keyboards alphabetically by language. } - - // Script to allow a user to add any keyboard to the keyboard menu + + // Script to allow a user to add any keyboard to the keyboard menu function addKeyboard(n) - { + { var sKbd,kmw=keyman; switch(n) { @@ -106,15 +106,22 @@ break; case 3: sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + kmw.addKeyboardsForLanguage(sKbd).then( + function(result) { + console.log('adding' + result) + }, + function(error) { + alert(error) + } + ); break; } } - + // Add keyboard on Enter (as well as pressing button) function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if(e.keyCode == 13) addKeyboard(id); } From 3fc120ffed3e8591e15826d2e3ca9e7986600a25 Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 11 Jun 2021 09:52:30 +0700 Subject: [PATCH 02/81] fix(web): fixes keymanCloudRequest --- web/source/keyboards/kmwkeyboards.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index decfde4c0e..e8ccf9f827 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1211,7 +1211,6 @@ namespace com.keyman.keyboards { keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise { var kbdManager = this; var keymanweb = this.keymanweb; - let currentKeyboardStubsCount = this.keyboardStubs.length; // Some basic support toward #5044, but definitely not a full solution toward it. // Wraps the cloud API keyboard-stub request in a Promise, allowing response on network @@ -1280,11 +1279,6 @@ namespace com.keyman.keyboards { } catch(ex) { document.getElementsByTagName('head')[0].appendChild(Lscript); } - // resolve? - //console.log(Lscript); - - // why is this empty - //resolve(kbdManager.keyboardStubs.slice(currentKeyboardStubsCount)); }); // TODO: Allow the site developer to handle error messaging via this catch. // This current version simply maintains pre-existing behavior. @@ -1293,8 +1287,7 @@ namespace com.keyman.keyboards { return Promise.reject(error); }); - // Return new keyboard stubs. If out of bounds (no new stubs), returns [] - return Promise.resolve(this.keyboardStubs.slice(currentKeyboardStubsCount)); + return promise; } /** From 9c93f318611ee113a996838a58327700522376f2 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 11 Jun 2021 12:14:52 +0700 Subject: [PATCH 03/81] fix(web): Cleanup params --- web/source/keyboards/kmwkeyboards.ts | 14 +++++++------- web/source/kmwbase.ts | 15 ++++++++++----- web/testing/commonHeader.js | 19 +++++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index e8ccf9f827..26f9bce316 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1065,7 +1065,7 @@ namespace com.keyman.keyboards { * * @param {Object} x metadata object **/ - register(x): KeyboardStub[] { + register(x) { const promiseid = x['timerid']; let result: KeyboardStub[] | Error; @@ -1089,8 +1089,6 @@ namespace com.keyman.keyboards { delete this.registrationResolvers[promiseid]; } } - - return result as KeyboardStub[]; } /** @@ -1134,7 +1132,9 @@ namespace com.keyman.keyboards { } else if(options['context'] == 'language') { // Download the full list of supported keyboard languages this.languageList = x['languages']; if(this.languagesPending) { - this.addLanguageKeyboards(this.languagesPending); + this.addLanguageKeyboards(this.languagesPending).then(result => { + return result + }); } this.languagesPending = []; } @@ -1147,9 +1147,9 @@ namespace com.keyman.keyboards { /** * Internal handler for processing keyboard registration, used only by `register` * - * @param {Object} languages Array of language names + * @param {string[]} languages Array of language names **/ - addLanguageKeyboards(languages: any): Promise { + addLanguageKeyboards(languages: string[]): Promise { var i, j, lgName, cmd, first, addAll; // Defer registering keyboards by language until the language list has been loaded @@ -1161,7 +1161,7 @@ namespace com.keyman.keyboards { } if(first) { - this.keymanCloudRequest('',true); + return this.keymanCloudRequest('',true); // then? } } else { // Identify and register each keyboard by language name cmd = ''; diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index e117991bdb..5cb5836a1a 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -305,10 +305,15 @@ namespace com.keyman { /** * Add default or all keyboards for a given language * - * @param {string} arg Language name (multiple arguments allowed) + * @param {string|string[]} arg Language name (multiple arguments allowed) **/ - ['addKeyboardsForLanguage'](arg) : Promise { - return this.keyboardManager.addLanguageKeyboards(arguments); + ['addKeyboardsForLanguage'](arg: string[]|string) : Promise { + console.log('type: ' + typeof arg); + if (typeof arg === 'string') { + return this.keyboardManager.addLanguageKeyboards(arg.split(',')); + } else { + return this.keyboardManager.addLanguageKeyboards(arg); + } } /** @@ -316,8 +321,8 @@ namespace com.keyman { * * @param {Object} x metadata object **/ - ['register'](x) : com.keyman.keyboards.KeyboardStub[] { - return this.keyboardManager.register(x); + ['register'](x) { + this.keyboardManager.register(x); } /** diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index d8948655c2..e8cacb9b0b 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -69,7 +69,13 @@ // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); + kmw.addKeyboardsForLanguage(['Dzongkha', 'Khmer']); + /* + kmw.addKeyboardsForLanguage('Khmer').then(result => { + console.log('Adding Spanish and Khmer:', result); + }).catch(error => { + alert(error) + });*/ // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', @@ -106,14 +112,11 @@ break; case 3: sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd).then( - function(result) { - console.log('adding' + result) - }, - function(error) { + kmw.addKeyboardsForLanguage(sKbd).then(result => { + console.log('adding', result); + }).catch(error => { alert(error) - } - ); + }); break; } } From 2e9cbc58ddd0e14dc612a903e58fb2c897a0c589 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Sun, 13 Jun 2021 08:24:50 +0700 Subject: [PATCH 04/81] fix(web): Add catch for errors --- web/source/keyboards/kmwkeyboards.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 26f9bce316..3c2ad165a3 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1134,6 +1134,9 @@ namespace com.keyman.keyboards { if(this.languagesPending) { this.addLanguageKeyboards(this.languagesPending).then(result => { return result + }).catch(error => { + console.log('swallow Error' + error); + //throw error }); } this.languagesPending = []; From 6899a526161243978fe653c189b651729c0d5338 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 14 Jun 2021 15:10:43 +0700 Subject: [PATCH 05/81] fix(web): Add nested promises --- web/source/keyboards/kmwkeyboards.ts | 41 ++++++++++++++++++++++------ web/testing/commonHeader.js | 4 +-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 3c2ad165a3..c6c7c3d0f6 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -124,6 +124,8 @@ namespace com.keyman.keyboards { */ registrationResolvers: {[timeoutID: number] : RegistrationPromiseTuple} = {}; + languageListPromise: Promise = null; + languageList: any[] = null; // List of keyboard languages available for KeymanCloud languagesPending: any[] = []; // Array of languages waiting to be registered @@ -1132,12 +1134,9 @@ namespace com.keyman.keyboards { } else if(options['context'] == 'language') { // Download the full list of supported keyboard languages this.languageList = x['languages']; if(this.languagesPending) { - this.addLanguageKeyboards(this.languagesPending).then(result => { - return result - }).catch(error => { - console.log('swallow Error' + error); - //throw error - }); + // Shouldn't be here because it's handled in addLanguageKeyboards promise + //return new Error('languagesPending should have been handled by addLanguageKeyboards promise'); + console.log('languagesPending (unexpected)', this.languagesPending); } this.languagesPending = []; } @@ -1156,16 +1155,39 @@ namespace com.keyman.keyboards { var i, j, lgName, cmd, first, addAll; // Defer registering keyboards by language until the language list has been loaded - if(this.languageList == null) { + if (this.languageList == null) { first = (this.languagesPending.length == 0); for(i=0; i { console.log('Adding Spanish and Khmer:', result); @@ -112,7 +112,7 @@ break; case 3: sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd).then(result => { + kmw.addKeyboardsForLanguage(['Khmer', 'Spanish']).then(result => { console.log('adding', result); }).catch(error => { alert(error) From 083d2ba711819fd80540284cc415c6bd310cba20 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 15 Jun 2021 12:27:27 +0700 Subject: [PATCH 06/81] fix(web): Use async/await for getting language list catalog --- web/source/keyboards/kmwkeyboards.ts | 62 ++++++++++++---------------- web/source/kmwbase.ts | 1 - web/testing/commonHeader.js | 20 +++++---- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index c6c7c3d0f6..adcb620fb4 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -93,7 +93,7 @@ namespace com.keyman.keyboards { return kbdid + ' keyboard not found.'; } - export interface RegistrationPromiseTuple { + interface RegistrationPromiseTuple { resolve: (stubs: KeyboardStub[]) => void; reject: (err: Error) => void; } @@ -1134,15 +1134,12 @@ namespace com.keyman.keyboards { } else if(options['context'] == 'language') { // Download the full list of supported keyboard languages this.languageList = x['languages']; if(this.languagesPending) { - // Shouldn't be here because it's handled in addLanguageKeyboards promise - //return new Error('languagesPending should have been handled by addLanguageKeyboards promise'); - console.log('languagesPending (unexpected)', this.languagesPending); + // There are still pending languages to add: + //console.info('languagesPending remain: ', this.languagesPending); } - this.languagesPending = []; + // addLanguageKeyboards will handle clearing languagesPending. } - //console.log(currentKeyboardStubsCount); - //console.log(this.keyboardStubs.slice(currentKeyboardStubsCount)); return this.keyboardStubs.slice(currentKeyboardStubsCount); } @@ -1150,44 +1147,40 @@ namespace com.keyman.keyboards { * Internal handler for processing keyboard registration, used only by `register` * * @param {string[]} languages Array of language names + * @returns {Promise { + async addLanguageKeyboards(languages: string[]): Promise { var i, j, lgName, cmd, first, addAll; + first = (this.languageList == null && this.languagesPending.length == 0); + // Defer registering keyboards by language until the language list has been loaded if (this.languageList == null) { - first = (this.languagesPending.length == 0); - for(i=0; i 0) { + let languageList = this.languagesPending; + this.languagesPending = []; + return this.addLanguageKeyboards(languageList); + } else { + let emptyStub: KeyboardStub[] = []; + console.log('return empty stub, initialResult: ', initialResult); + return Promise.resolve(emptyStub); + } } else { // Identify and register each keyboard by language name cmd = ''; for(i=0; i { - console.log('type: ' + typeof arg); if (typeof arg === 'string') { return this.keyboardManager.addLanguageKeyboards(arg.split(',')); } else { diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index 66e1fda9d5..5203efef19 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -69,13 +69,16 @@ // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); - /* - kmw.addKeyboardsForLanguage('Khmer').then(result => { - console.log('Adding Spanish and Khmer:', result); + kmw.addKeyboardsForLanguage('Dzongkha').then(result => { + console.log('adding Dzongkha: ', result); + }).catch(error => { + alert(error); + }); + kmw.addKeyboardsForLanguage('Thai').then(result => { + console.log('adding Thai:', result); }).catch(error => { alert(error) - });*/ + }); // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', @@ -111,11 +114,12 @@ kmw.addKeyboards('@'+sKbd); break; case 3: + // Add keyboards by comma-separated languages sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(['Khmer', 'Spanish']).then(result => { - console.log('adding', result); + kmw.addKeyboardsForLanguage(sKbd.split(',').map(item => item.trim())).then(result => { + console.log('adding ', sKbd, ': ', result); }).catch(error => { - alert(error) + alert(error) }); break; } From 2c0f8e85665728c95bf3c80e175881e926a59617 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 15 Jun 2021 12:59:00 +0700 Subject: [PATCH 07/81] chore(web): Minor cleanup --- web/source/keyboards/kmwkeyboards.ts | 4 ++-- web/source/kmwbase.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index adcb620fb4..40f3ce329f 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1084,7 +1084,7 @@ namespace com.keyman.keyboards { try { if(result instanceof Error) { promiseFuncs.reject(result as Error); - } else if (promiseFuncs) { + } else { promiseFuncs.resolve(result as KeyboardStub[]); } } finally { @@ -1177,8 +1177,8 @@ namespace com.keyman.keyboards { this.languagesPending = []; return this.addLanguageKeyboards(languageList); } else { + // Is this the correct to return an empty stub? Can't return Pending let emptyStub: KeyboardStub[] = []; - console.log('return empty stub, initialResult: ', initialResult); return Promise.resolve(emptyStub); } } else { // Identify and register each keyboard by language name diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index 6119cfb712..896e90c883 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -303,9 +303,10 @@ namespace com.keyman { } /** - * Add default or all keyboards for a given language + * Add default keyboard for given language(s) * - * @param {string|string[]} arg Language name (multiple arguments allowed) + * @param {string|string[]} arg Language name(s) (multiple arguments allowed) + * @returns {Promise} Promise of added keyboard stubs **/ ['addKeyboardsForLanguage'](arg: string[]|string) : Promise { if (typeof arg === 'string') { From 59f26a0e6433a59622b294700f711c7de8bbe978 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 16 Jun 2021 09:26:59 +0700 Subject: [PATCH 08/81] chore(web): Remove Error return type --- web/source/keyboards/kmwkeyboards.ts | 9 ++++----- web/source/kmwbase.ts | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 40f3ce329f..1fca1d5662 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -124,8 +124,6 @@ namespace com.keyman.keyboards { */ registrationResolvers: {[timeoutID: number] : RegistrationPromiseTuple} = {}; - languageListPromise: Promise = null; - languageList: any[] = null; // List of keyboard languages available for KeymanCloud languagesPending: any[] = []; // Array of languages waiting to be registered @@ -1147,9 +1145,9 @@ namespace com.keyman.keyboards { * Internal handler for processing keyboard registration, used only by `register` * * @param {string[]} languages Array of language names - * @returns {Promise { + async addLanguageKeyboards(languages: string[]): Promise { var i, j, lgName, cmd, first, addAll; first = (this.languageList == null && this.languagesPending.length == 0); @@ -1223,8 +1221,9 @@ namespace com.keyman.keyboards { * * @param {string} cmd command string * @param {boolean?} byLanguage if true, context=languages, else context=keyboards + * @returns {Promise} Promise of added keyboard stubs **/ - keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise { + keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise { var kbdManager = this; var keymanweb = this.keymanweb; diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index 896e90c883..a36cf0745c 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -306,9 +306,9 @@ namespace com.keyman { * Add default keyboard for given language(s) * * @param {string|string[]} arg Language name(s) (multiple arguments allowed) - * @returns {Promise} Promise of added keyboard stubs + * @returns {Promise { + ['addKeyboardsForLanguage'](arg: string[]|string) : Promise { if (typeof arg === 'string') { return this.keyboardManager.addLanguageKeyboards(arg.split(',')); } else { From e81a7c16115818015dda53aaf8679a57a7a9ce2a Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 16 Jun 2021 17:57:17 +0700 Subject: [PATCH 09/81] fix(web): Fix return of nested promises --- web/source/keyboards/kmwkeyboards.ts | 92 ++++++++++++++++++---------- web/source/kmwbase.ts | 8 +-- web/testing/commonHeader.js | 13 ++-- web/testing/unminified.html | 80 ++++++++++++------------ 4 files changed, 107 insertions(+), 86 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 1fca1d5662..e77be8a916 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -108,6 +108,8 @@ namespace com.keyman.keyboards { activeStub: KeyboardStub = null; keyboardStubs: KeyboardStub[] = []; + firstCall: boolean = true; // First time to call keymanCloudRequest() + deferredStubs: any[] = []; // The list of user-provided keyboard stub registration objects. deferredKRS = []; // Array of pending keyboard stubs from KRS, to register after initialization deferredKR = []; // Array of pending keyboards, to be installed at end of initialization @@ -124,8 +126,9 @@ namespace com.keyman.keyboards { */ registrationResolvers: {[timeoutID: number] : RegistrationPromiseTuple} = {}; + languageListPromise: Promise = null; + languageList: any[] = null; // List of keyboard languages available for KeymanCloud - languagesPending: any[] = []; // Array of languages waiting to be registered linkedScripts: HTMLScriptElement[] = []; @@ -1131,11 +1134,6 @@ namespace com.keyman.keyboards { } } else if(options['context'] == 'language') { // Download the full list of supported keyboard languages this.languageList = x['languages']; - if(this.languagesPending) { - // There are still pending languages to add: - //console.info('languagesPending remain: ', this.languagesPending); - } - // addLanguageKeyboards will handle clearing languagesPending. } return this.keyboardStubs.slice(currentKeyboardStubsCount); @@ -1145,40 +1143,54 @@ namespace com.keyman.keyboards { * Internal handler for processing keyboard registration, used only by `register` * * @param {string[]} languages Array of language names - * @returns {Promise} Promise of added keyboard stubs **/ - async addLanguageKeyboards(languages: string[]): Promise { - var i, j, lgName, cmd, first, addAll; - - first = (this.languageList == null && this.languagesPending.length == 0); + addLanguageKeyboards(languages: string[]): Promise { + var i, j, lgName, cmd, addAll, retPromise; // Defer registering keyboards by language until the language list has been loaded if (this.languageList == null) { - for(i=0; i 0) { - let languageList = this.languagesPending; - this.languagesPending = []; - return this.addLanguageKeyboards(languageList); - } else { - // Is this the correct to return an empty stub? Can't return Pending - let emptyStub: KeyboardStub[] = []; - return Promise.resolve(emptyStub); - } + }).then(function(result) { + if (result instanceof Error) { + return Promise.reject(result); + } + return Promise.resolve(result); + }).catch(function(error) { + return Promise.reject(error); + }); + } else { // Identify and register each keyboard by language name cmd = ''; for(i=0; i} Promise of added keyboard stubs **/ ['addKeyboardsForLanguage'](arg: string[]|string) : Promise { if (typeof arg === 'string') { - return this.keyboardManager.addLanguageKeyboards(arg.split(',')); + return this.keyboardManager.addLanguageKeyboards(arg.split(',').map(item => item.trim())); } else { return this.keyboardManager.addLanguageKeyboards(arg); } diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index 5203efef19..85362a3943 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -70,15 +70,10 @@ // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) kmw.addKeyboardsForLanguage('Dzongkha').then(result => { - console.log('adding Dzongkha: ', result); + console.log('Adding Dzonkha: ', result); }).catch(error => { alert(error); }); - kmw.addKeyboardsForLanguage('Thai').then(result => { - console.log('adding Thai:', result); - }).catch(error => { - alert(error) - }); // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', @@ -114,12 +109,12 @@ kmw.addKeyboards('@'+sKbd); break; case 3: - // Add keyboards by comma-separated languages + // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; kmw.addKeyboardsForLanguage(sKbd.split(',').map(item => item.trim())).then(result => { - console.log('adding ', sKbd, ': ', result); + console.log('adding ', sKbd, ': ', result); }).catch(error => { - alert(error) + alert(error) }); break; } diff --git a/web/testing/unminified.html b/web/testing/unminified.html index 0e850a8ce9..4f1a28c731 100644 --- a/web/testing/unminified.html +++ b/web/testing/unminified.html @@ -2,38 +2,38 @@ - - - + + + - + - - + + KeymanWeb Sample Page - Unminified Source - - - - + #KeymanWebControl {width:50%;min-width:600px;} + + + - - + - + + - + - - + +

KeymanWeb Sample Page - Unminified Source

- -
+

Type in your language in this text area:

- +

or in this input field:

- +

Add a keyboard by keyboard name:

- +

Add a keyboard by BCP-47 language code:

-

Add a keyboard by language name:

+

Add a keyboard by language name(s):

- +

Return to testing home page

- - + * + *** + --> From d7d5ad085dd02b8fc3388cb86d558371d5a57e2f Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 16 Jun 2021 21:44:02 +0700 Subject: [PATCH 10/81] chore(web): Update preamble --- web/source/kmwbase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index bdc72aac46..b8c2b27e15 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -303,7 +303,7 @@ namespace com.keyman { } /** - * Add default or all keyboards for a given language + * Add default keyboards for given language(s) * * @param {string|string[]} arg Language name (multiple arguments allowed) * @returns {Promise} Promise of added keyboard stubs From d599a406f3be57eeb3549a7cb3baf783fb7e4512 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 17 Jun 2021 08:58:08 +0700 Subject: [PATCH 11/81] Apply suggestions from code review Co-authored-by: Joshua Horton --- web/source/keyboards/kmwkeyboards.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index e77be8a916..7f7cf7fea8 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1155,7 +1155,7 @@ namespace com.keyman.keyboards { let promise = this.keymanCloudRequest('',true); // If promise is not error, then... (needs an error guard) promise.catch(function(error) { - console.log('first error', error); + console.error("Unable to retrieve the master language list.") return Promise.reject(error); }); this.languageListPromise = promise; @@ -1167,15 +1167,7 @@ namespace com.keyman.keyboards { // 1: wait for the language list to be loaded properly _this.languageListPromise.then(function() { // 2: perform the actual query, now that we can find the language code - let innerPromise = _this.addLanguageKeyboards(languages); - - // 3: intercept the Promise resolve/reject and pass them to the - // returned Promise. - innerPromise.then(function(stubs) { - resolve(stubs); - }).catch(function(error) { - reject(error); - }); + resolve(_this.addLanguageKeyboards(languages)); }).catch(function(error) { return Promise.reject(error); }); @@ -1631,4 +1623,4 @@ namespace com.keyman.keyboards { } } } -} \ No newline at end of file +} From 8633c07516b204ab8a37cad2e87d293cf99f0b39 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 17 Jun 2021 10:53:58 +0700 Subject: [PATCH 12/81] chore(web): More cleanup * Clarify TODO * Consolidate addKeyboardsForLanguage() call in commonHeader.js --- web/source/keyboards/kmwkeyboards.ts | 5 +++-- web/testing/commonHeader.js | 27 +++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 7f7cf7fea8..198c060fc3 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1209,7 +1209,8 @@ namespace com.keyman.keyboards { } } if (!languageFound) { - // TODO: Add error msg that no keyboards are available for lgName + // TODO: Construct response array of errors (failed-query keyboards) + // that will be merged with stubs (successfully-queried keyboards) console.error('No keyboards are available for '+lgName+'. '+ 'Does it have another language name?'); } @@ -1229,7 +1230,7 @@ namespace com.keyman.keyboards { if (retPromise) { return retPromise; } else { - console.log('Should have something here'); + // No keyboards added so return empty stub return Promise.resolve([]); } } diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index 85362a3943..95f6f024d8 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -40,6 +40,21 @@ and is called when the page loads. */ + /** + * Add a keyboard by language name. Note that the name must be spelled + correctly, or the keyboard will not be found. (Using BCP-47 codes is + usually easier.) + * @param {string} languages. Could be comma-separated languages + */ + function addKeyboardsForLanguage(languages) { + kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => { + console.log('Adding ' + languages + ': ', result); + }).catch(error => { + // Consumer decides how to handle errors + alert(error); + }); + } + function loadKeyboards(nestLevel) { var kmw=keyman; @@ -69,11 +84,7 @@ // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha').then(result => { - console.log('Adding Dzonkha: ', result); - }).catch(error => { - alert(error); - }); + addKeyboardsForLanguage('Dzongkha'); // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', @@ -111,11 +122,7 @@ case 3: // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd.split(',').map(item => item.trim())).then(result => { - console.log('adding ', sKbd, ': ', result); - }).catch(error => { - alert(error) - }); + addKeyboardsForLanguage(sKbd); break; } } From e0c2cca91924f982fb6a7ad8a6e7d02b13f1ab0d Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 17 Jun 2021 11:16:38 +0700 Subject: [PATCH 13/81] test(web): Make separate test page for Promise API --- web/testing/commonHeader.js | 19 +--- web/testing/index.html | 1 + web/testing/promise-api/index.html | 105 +++++++++++++++++++++ web/testing/promise-api/promisehdr.js | 126 ++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 web/testing/promise-api/index.html create mode 100644 web/testing/promise-api/promisehdr.js diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index 95f6f024d8..d1f0ed9594 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -40,21 +40,6 @@ and is called when the page loads. */ - /** - * Add a keyboard by language name. Note that the name must be spelled - correctly, or the keyboard will not be found. (Using BCP-47 codes is - usually easier.) - * @param {string} languages. Could be comma-separated languages - */ - function addKeyboardsForLanguage(languages) { - kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => { - console.log('Adding ' + languages + ': ', result); - }).catch(error => { - // Consumer decides how to handle errors - alert(error); - }); - } - function loadKeyboards(nestLevel) { var kmw=keyman; @@ -84,7 +69,7 @@ // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - addKeyboardsForLanguage('Dzongkha'); + kmw.addKeyboardsForLanguage('Dzongkha'); // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', @@ -122,7 +107,7 @@ case 3: // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - addKeyboardsForLanguage(sKbd); + kmw.addKeyboardsForLanguage(sKbd); break; } } diff --git a/web/testing/index.html b/web/testing/index.html index 78ed344a5a..88df0ee8b1 100644 --- a/web/testing/index.html +++ b/web/testing/index.html @@ -29,6 +29,7 @@

Platform testing

Prediction UI testing

Prediction - robust testing

+

Promise API testing

Toolbar UI testing

Tests keyboard documentation rendering.

Tests Sentry error-reporting functionality

diff --git a/web/testing/promise-api/index.html b/web/testing/promise-api/index.html new file mode 100644 index 0000000000..e545672d17 --- /dev/null +++ b/web/testing/promise-api/index.html @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + KeymanWeb Testing Page - Promise API when Adding Keyboards + + + + + + + + + + + + + + + + + + + + +

KeymanWeb Sample Page - Promise API

+

This page is for testing the Promises returned from adding keyboards. + See Specification #5044 +

+
+ +
+ +

Type in your language in this text area:

+ + +

or in this input field:

+ + + +

Add a keyboard by keyboard name:

+ + + +

Add a keyboard by BCP-47 language code:

+ + + +

Add a keyboard by comma-separated language name(s):

+ + + +

Return to testing home page

+
+ + + + + diff --git a/web/testing/promise-api/promisehdr.js b/web/testing/promise-api/promisehdr.js new file mode 100644 index 0000000000..16169dc274 --- /dev/null +++ b/web/testing/promise-api/promisehdr.js @@ -0,0 +1,126 @@ +// JavaScript Document promisehdr.js: Keyboard management for KeymanWeb demonstration pages + +/* + The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available. + If the same keyboard is used for several languages, it must be listed for each + language, but the keyboard itself will only be loaded once. + If two (or more) keyboards are to be available for a given language, both must be listed. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + The actual keyboard object will be downloaded asynchronously when first selected for use. + + Each argument to addKeyboards() is a string, for example: + european2 loads the current version of the Eurolatin 2 keyboard (for its default language) + european2@fr loads the current version of the Eurolatin 2 keyboard for French + european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French + + Argument syntax also supports the following extensions: + @fr load the current version of the default keyboard for French + @fr$ load all available keyboards (current version) for French + + Each call to addKeyboards() requires a single call to the remote server, + (unless all keyboards listed are local and fully specified) so it is better + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select + the appropriate language and keyboard in this case. + + Keyboards may also be specified by language name using addKeyboardsForLanguage() + for example: + keymanweb.addKeyboardsForLanguage('Burmese'); + + Appending $ to the language name will again cause all available keyboards for that + language to be loaded rather than the default keyboard. + + The first call to addKeyboardsForLanguage() makes an additional call to the + keyman API to load the current list of keyboard/language associations. + + In this example, the following function loads the indicated keyboards, + and is called when the page loads. +*/ + + /** + * Add a keyboard by language name. Note that the name must be spelled + correctly, or the keyboard will not be found. (Using BCP-47 codes is + usually easier.) + * @param {string} languages. Could be comma-separated languages + */ + function addKeyboardsForLanguage(languages) { + kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => { + console.log('Adding ' + languages + ': ', result); + }).catch(error => { + // Consumer decides how to handle errors + alert(error); + }); + } + + function loadKeyboards() + { + var kmw=keyman; + + // The first keyboard added will be the default keyboard for touch devices. + // For faster loading, it may be best for the default keyboard to be + // locally sourced. + kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + filename:'../us-1.0.js'}); + + // Add more keyboards to the language menu, by keyboard name, + // keyboard name and language code, or just the BCP-47 language code. + // We use a different loading pattern here than in the samples version to provide a slightly different set of test cases. + kmw.addKeyboards('french','@he'); + kmw.addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string. + + // Add a keyboard by language name. Note that the name must be spelled + // correctly, or the keyboard will not be found. (Using BCP-47 codes is + // usually easier.) + addKeyboardsForLanguage('Dzongkha'); + + // Add a fully-specified, locally-sourced, keyboard with custom font + kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', + languages: { + id:'lo',name:'Lao',region:'Asia' + }, + filename:'../lao_2008_basic-1.2.js' + }); + + // The following two optional calls should be delayed until language menus are fully loaded: + // (a) a specific mapped input element input is focused, to ensure that the OSK appears + // (b) a specific keyboard is loaded, rather than the keyboard last used. + //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); + //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); + + // Note that locally specified keyboards will be listed before keyboards + // requested from the remote server by user interfaces that do not order + // keyboards alphabetically by language. + } + + // Script to allow a user to add any keyboard to the keyboard menu + function addKeyboard(n) + { + var sKbd,kmw=keyman; + switch(n) + { + case 1: + sKbd=document.getElementById('kbd_id1').value; + kmw.addKeyboards(sKbd); + break; + case 2: + sKbd=document.getElementById('kbd_id2').value.toLowerCase(); + kmw.addKeyboards('@'+sKbd); + break; + case 3: + // Add keyboard for comma-separated language name(s) + sKbd=document.getElementById('kbd_id3').value; + addKeyboardsForLanguage(sKbd); + break; + } + } + + // Add keyboard on Enter (as well as pressing button) + function clickOnEnter(e,id) + { + e = e || window.event; + if(e.keyCode == 13) addKeyboard(id); + } + From b2636603d831831929ba047b26de5329c28d2698 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 17 Jun 2021 13:03:56 +0700 Subject: [PATCH 14/81] feat(web): Add option useAlerts to control alerts() --- web/source/keyboards/kmwkeyboards.ts | 23 ++++++++++++++++------ web/source/kmwbase.ts | 5 ++++- web/source/kmwutils.ts | 26 +++++++++++++------------ web/testing/promise-api/index.html | 29 ++++++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 198c060fc3..fdb9c791ed 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1211,16 +1211,13 @@ namespace com.keyman.keyboards { if (!languageFound) { // TODO: Construct response array of errors (failed-query keyboards) // that will be merged with stubs (successfully-queried keyboards) - console.error('No keyboards are available for '+lgName+'. '+ - 'Does it have another language name?'); + let msg = this.languageUnavailable(lgName); + console.error(msg); } } if(cmd == '') { - // TODO: Check useAlert flag to determine whether or not KeymanWeb should display its own alert messages - let msg = 'No keyboards are available for '+languages[0]+'. ' - +'Does it have another language name?'; - this.keymanweb.util.alert(msg); + let msg = this.languageUnavailable(languages[0]); return Promise.reject(new Error(msg)); } else { return this.keymanCloudRequest('&keyboardid='+cmd, false); @@ -1324,6 +1321,20 @@ namespace com.keyman.keyboards { return promise; } + /** + * Display warning if language name unavailable to add keyboard + * @param {string} languageName + * @returns string of Error message + */ + private languageUnavailable(languageName: string): string { + let msg = 'No keyboards are available for '+ languageName + '. ' + +'Does it have another language name?'; + if (this.keymanweb.options['useAlerts']=='true') { + this.keymanweb.util.alert(msg); + } + return msg; + } + /** * Display warning if Keyman Cloud server fails to respond * diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index b8c2b27e15..d2777dfbd7 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -80,7 +80,10 @@ namespace com.keyman { 'fonts':'', 'attachType':'', 'ui':null, - 'setActiveOnRegister':'true' + 'setActiveOnRegister':'true', + + // Determines whether or not KeymanWeb should display its own alert messages + 'useAlerts':'true' }; diff --git a/web/source/kmwutils.ts b/web/source/kmwutils.ts index f5c8bed9ee..57174a2e09 100644 --- a/web/source/kmwutils.ts +++ b/web/source/kmwutils.ts @@ -715,7 +715,7 @@ namespace com.keyman { } if(eot != '' && (eot.indexOf('/') < 0)) { - eot = this.keyman.options['fonts']+eot; + eot = this.keyman.options['fonts']+eot; } if(svg != '' && (svg.indexOf('/') < 0)) { @@ -951,22 +951,24 @@ namespace com.keyman { } /** - * Customized alert + * Customized alert. This is enabled/disabled by the option flag 'useAlerts' * * @param {string} s alert text * @param {function()=} fn function to call when alert dismissed */ alert(s: string, fn?: () => void): void { - var bg = this.waiting, nn=bg.firstChild.childNodes; - (nn[0] as HTMLElement).style.display='block'; - (nn[1] as HTMLElement).className='kmw-alert-text'; - (nn[1] as HTMLElement).innerHTML=s; - (nn[2] as HTMLElement).style.display='none'; - bg.style.display='block'; - if(arguments.length > 1) { - bg.dismiss=fn; - } else { - bg.dismiss=null; + if (this.keyman.options['useAlerts']=='true') { + var bg = this.waiting, nn=bg.firstChild.childNodes; + (nn[0] as HTMLElement).style.display='block'; + (nn[1] as HTMLElement).className='kmw-alert-text'; + (nn[1] as HTMLElement).innerHTML=s; + (nn[2] as HTMLElement).style.display='none'; + bg.style.display='block'; + if(arguments.length > 1) { + bg.dismiss=fn; + } else { + bg.dismiss=null; + } } } diff --git a/web/testing/promise-api/index.html b/web/testing/promise-api/index.html index e545672d17..640b94191a 100644 --- a/web/testing/promise-api/index.html +++ b/web/testing/promise-api/index.html @@ -36,9 +36,24 @@ +
- - + - + // Thank you to https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams + const attachType = (new URLSearchParams(window.location.search)).get("mode"); + var attachText = attachType ? attachType : "default"; + + var kmw=window.keyman; + if (attachType) { + kmw.init({ + attachType: attachType}); + } else { + kmw.init({}); + } + + - - + + - +

KeymanWeb Sample Page - Attachment API Testing

This page is designed to stress-test the new attachment/enablement API model and its functions.

-

You are currently testing under the attachment mode. Two other modes are available: - Auto Manual +

You are currently testing under the attachment mode. Two other modes are available: + Auto Manual Default

Be sure to reference the developer console for additional feedback.

-

Note that the 'attachment' state reported is managed by this page and not reported from KeymanWeb itself; +

Note that the 'attachment' state reported is managed by this page and not reported from KeymanWeb itself; take it as a guide at best. (Especially if on a touch device.)

From c8f7a4cdf4a3d01fe5cbbcc74ff930c7212a37df Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 24 Jun 2021 13:02:49 +0700 Subject: [PATCH 40/81] fix(web): Use util.internalAlert() wrapper --- web/source/keyboards/kmwkeyboards.ts | 18 +++++------------- web/source/kmwutils.ts | 12 ++++++++++++ web/source/osk/oskManager.ts | 4 +--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index ed62d5f296..d8bd6ec15d 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -570,11 +570,9 @@ namespace com.keyman.keyboards { // Thanks, Closure errors. if(!this.keymanweb.isEmbedded) { util.wait(false); - if (this.keymanweb.options.useAlerts) { - util.alert(altString || msg, function() { + util.internalAlert(altString || msg, function() { this.keymanweb['setActiveKeyboard'](''); // The API call! - }.bind(this)); - } + }.bind(this)); } switch(msgType) { // in case we extend this later. @@ -949,9 +947,7 @@ namespace com.keyman.keyboards { if(typeof(x[i]['filename']) == 'string') { if(!this.addStub(x[i])) { - if (this.keymanweb.options.useAlerts) { - this.keymanweb.util.alert('To use a custom keyboard, you must specify file name, keyboard name, language, language code and region code.'); - } + this.keymanweb.util.internalAlert('To use a custom keyboard, you must specify file name, keyboard name, language, language code and region code.'); } } else { if(x[i]['language']) { @@ -1331,9 +1327,7 @@ namespace com.keyman.keyboards { private alertLanguageUnavailable(languageName: string): string { let msg = 'No keyboards are available for '+ languageName + '. ' +'Does it have another language name?'; - if (this.keymanweb.options.useAlerts) { - this.keymanweb.util.alert(msg); - } + this.keymanweb.util.internalAlert(msg); return msg; } @@ -1344,9 +1338,7 @@ namespace com.keyman.keyboards { * **/ private serverUnavailable(cmd) { - if (this.keymanweb.options.useAlerts) { - this.keymanweb.util.alert(cmd == '' ? 'Unable to connect to Keyman Cloud server!' : cmd); - } + this.keymanweb.util.internalAlert(cmd == '' ? 'Unable to connect to Keyman Cloud server!' : cmd); this.keymanweb.warned=true; } diff --git a/web/source/kmwutils.ts b/web/source/kmwutils.ts index 1915d8f119..36f6f7211c 100644 --- a/web/source/kmwutils.ts +++ b/web/source/kmwutils.ts @@ -971,6 +971,18 @@ namespace com.keyman { } + /** + * Customized internal alert. This is enabled/disabled by the option flag 'useAlerts' + * + * @param {string} s alert text + * @param {function()=} fn function to call when alert dismissed + */ + internalAlert(s: string, fn?: () => void): void { + if (this.keyman.options.useAlerts) { + this.alert(s, fn); + } + } + /** * Prepare the background and keyboard loading wait message box * Should not be called before options are defined during initialization diff --git a/web/source/osk/oskManager.ts b/web/source/osk/oskManager.ts index 4cbea24ba2..04f089ac6c 100644 --- a/web/source/osk/oskManager.ts +++ b/web/source/osk/oskManager.ts @@ -465,10 +465,8 @@ namespace com.keyman.osk { */ showBuild() { let keymanweb = com.keyman.singleton; - if (keymanweb.options.useAlerts) { - keymanweb.util.alert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'

' + keymanweb.util.internalAlert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'

' +'Copyright © 2017 SIL International'); - } } /** From fbccf18fcde12f26c2cb737eff626a5c7f204ae5 Mon Sep 17 00:00:00 2001 From: Gunnar Hjalmarsson Date: Wed, 23 Jun 2021 15:28:27 +0200 Subject: [PATCH 41/81] fix(linux): Add libglib2.0-bin to keyman Depends Fixes #5344 (cherry picked from commit 12e2c4c2c1880093b344c0e147daf32bb1f45e55) --- linux/keyman-config/debian/changelog | 7 +++++++ linux/keyman-config/debian/control | 1 + 2 files changed, 8 insertions(+) diff --git a/linux/keyman-config/debian/changelog b/linux/keyman-config/debian/changelog index 763386f9b8..7e469baba7 100644 --- a/linux/keyman-config/debian/changelog +++ b/linux/keyman-config/debian/changelog @@ -1,3 +1,10 @@ +keyman-config (14.0.276-2) experimental; urgency=medium + + * Team upload + * Add libglib2.0-bin to keyman Depends (closes: #990226) + + -- Gunnar Hjalmarsson Wed, 23 Jun 2021 15:28:27 +0200 + keyman-config (14.0.276-1) experimental; urgency=medium * New upstream release diff --git a/linux/keyman-config/debian/control b/linux/keyman-config/debian/control index 2e844fc96b..4368565fe0 100644 --- a/linux/keyman-config/debian/control +++ b/linux/keyman-config/debian/control @@ -34,6 +34,7 @@ Homepage: https://www.keyman.com Package: keyman Architecture: all Depends: + libglib2.0-bin, python3-keyman-config, ${misc:Depends}, Recommends: From cafbdef35aa24582dd3ddf364563cc24bbe7a4a5 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 10:12:08 +0200 Subject: [PATCH 42/81] fix(linux): fix tab completion for command line tools Fixes #5306 --- .../debian/keyman.bash-completion | 1 + linux/keyman-config/km-config | 5 ++- linux/keyman-config/km-config.bash-completion | 36 +++++++++++++++++++ .../keyman-config/km-kvk2ldml.bash-completion | 15 ++++++++ .../km-package-get.bash-completion | 26 +++++++------- .../km-package-install.bash-completion | 8 ++++- 6 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 linux/keyman-config/km-config.bash-completion diff --git a/linux/keyman-config/debian/keyman.bash-completion b/linux/keyman-config/debian/keyman.bash-completion index cce485f8a4..bd000c8efd 100644 --- a/linux/keyman-config/debian/keyman.bash-completion +++ b/linux/keyman-config/debian/keyman.bash-completion @@ -1,3 +1,4 @@ +km-config.bash-completion km-config km-kvk2ldml.bash-completion km-kvk2ldml km-package-get.bash-completion km-package-get km-package-install.bash-completion km-package-install diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index 149c857ae6..4345d25a1c 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -16,7 +16,10 @@ if __name__ == '__main__': parser.add_argument('--version', action='version', version='%(prog)s version ' + versionstring) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') - parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp package') + parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp ' + + 'package. INSTALL can either be a downloaded .kmp file, a file:// URL ' + + 'pointing to a .kmp file, or a keyman:// URL, possibly with a ' + + 'bcp47= specified.') args = parser.parse_args() if args.verbose: diff --git a/linux/keyman-config/km-config.bash-completion b/linux/keyman-config/km-config.bash-completion new file mode 100644 index 0000000000..bc28b19212 --- /dev/null +++ b/linux/keyman-config/km-config.bash-completion @@ -0,0 +1,36 @@ +#/usr/bin/env bash + +_km-config_completions() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-h --help -v --verbose -vv --veryverbose --version -i --install" + + if [[ ${cur} == -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + # default IFS splits 'file://' into 'file', ':' and '//' + if [ "${prev}" == ":" -a "${COMP_WORDS[COMP_CWORD-2]}" == "file" ]; then + local flag=${COMP_WORDS[COMP_CWORD-3]} + if [ $flag == "-i" -o $flag == "--install" ]; then + prev=$flag + fi + fi + + case "${prev}" in + "-i"|"--install") + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) ) + return 0 + ;; + *) + ;; + esac +} + +complete -F _km-config_completions km-config diff --git a/linux/keyman-config/km-kvk2ldml.bash-completion b/linux/keyman-config/km-kvk2ldml.bash-completion index ab40c3ef21..c89967666f 100644 --- a/linux/keyman-config/km-kvk2ldml.bash-completion +++ b/linux/keyman-config/km-kvk2ldml.bash-completion @@ -12,6 +12,21 @@ _km-kvk2ldml_completions() COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi + + case "${prev}" in + "-o"|"--output") + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -- $cur) ) + return 0 + ;; + *) + ;; + esac + + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -X "!"*.kvk -- $cur) $(compgen -d -- $cur) ) } complete -F _km-kvk2ldml_completions km-kvk2ldml diff --git a/linux/keyman-config/km-package-get.bash-completion b/linux/keyman-config/km-package-get.bash-completion index 48b96b942f..a9b0cc7fd7 100644 --- a/linux/keyman-config/km-package-get.bash-completion +++ b/linux/keyman-config/km-package-get.bash-completion @@ -13,20 +13,20 @@ _km-package-get_completions() return 0 fi - words="" - if [[ ! -e ~/.cache/keyman/kmpdirlist ]] ; then - if [[ -e ./km-package-install ]]; then - python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" - else - python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()" - fi - fi + words="" + if [[ ! -e ~/.cache/keyman/kmpdirlist ]] ; then + if [[ -e ./km-package-install ]]; then + python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" + else + python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()" + fi + fi - if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then - for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done - COMPREPLY=($(compgen -W "${words}" -- ${cur})) - return 0 - fi + if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then + for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done + COMPREPLY=($(compgen -W "${words}" -- ${cur})) + return 0 + fi } complete -F _km-package-get_completions km-package-get diff --git a/linux/keyman-config/km-package-install.bash-completion b/linux/keyman-config/km-package-install.bash-completion index 8833ed411e..72bebfb383 100644 --- a/linux/keyman-config/km-package-install.bash-completion +++ b/linux/keyman-config/km-package-install.bash-completion @@ -14,7 +14,7 @@ _km-package-install_completions() fi case "${prev}" in - "-k"|"--keyboardid") + "-p"|"--package") words="" if [[ ! -s ~/.cache/keyman/kmpdirlist ]] ; then if [[ -e ./km-package-install ]]; then @@ -30,6 +30,12 @@ _km-package-install_completions() return 0 fi ;; + "-f"|"--file") + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) ) + return 0 + ;; *) ;; esac From 2831084b87c2c857e5bbe60d26a34ede6c503be8 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 14:54:03 +0200 Subject: [PATCH 43/81] fix(linux): respect XDG_CACHE_HOME environment variable --- linux/keyman-config/km-package-get.bash-completion | 10 ++++++---- linux/keyman-config/km-package-install.bash-completion | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/linux/keyman-config/km-package-get.bash-completion b/linux/keyman-config/km-package-get.bash-completion index a9b0cc7fd7..1e8f66dab3 100644 --- a/linux/keyman-config/km-package-get.bash-completion +++ b/linux/keyman-config/km-package-get.bash-completion @@ -2,19 +2,21 @@ _km-package-get_completions() { - local cur prev opts + local cur prev opts cache COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -v --verbose -vv --veryverbose --version" + cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist + if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi words="" - if [[ ! -e ~/.cache/keyman/kmpdirlist ]] ; then + if [[ ! -e $cache ]] ; then if [[ -e ./km-package-install ]]; then python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" else @@ -22,8 +24,8 @@ _km-package-get_completions() fi fi - if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then - for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done + if [[ -r $cache ]] ; then + for file in `cat $cache`; do words="${words} ${file}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) return 0 fi diff --git a/linux/keyman-config/km-package-install.bash-completion b/linux/keyman-config/km-package-install.bash-completion index 72bebfb383..4525c930fa 100644 --- a/linux/keyman-config/km-package-install.bash-completion +++ b/linux/keyman-config/km-package-install.bash-completion @@ -2,12 +2,14 @@ _km-package-install_completions() { - local cur prev opts + local cur prev opts cache COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-h --help -v --verbose -vv --veryverbose --version -p --package -f --file -s --shared" + cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist + if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 @@ -16,7 +18,7 @@ _km-package-install_completions() case "${prev}" in "-p"|"--package") words="" - if [[ ! -s ~/.cache/keyman/kmpdirlist ]] ; then + if [[ ! -s $cache ]] ; then if [[ -e ./km-package-install ]]; then python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()" else @@ -24,8 +26,8 @@ _km-package-install_completions() fi fi - if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then - for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done + if [[ -r $cache ]] ; then + for file in `cat $cache`; do words="${words} ${file}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) return 0 fi From 70b88790dff7ff64b6102cb57be99121e1978689 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 16:09:27 +0200 Subject: [PATCH 44/81] fix(linux): Don't add duplicate entries when reinstalling keyboard When we update/reinstall an existing keyboard we don't want to add a new entry to the list of keyboards. Fixes #5308. --- linux/keyman-config/keyman_config/install_kmp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index aa36bfed60..8f498f9e73 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -228,7 +228,9 @@ class InstallKmp(): # install all kmx for first lang not just packageID for kb in keyboards: ibus_keyboard_id = get_ibus_keyboard_id(kb, packageDir, language) - sources.append(('ibus', ibus_keyboard_id)) + input_source = ('ibus', ibus_keyboard_id) + if input_source not in sources: + sources.append(input_source) gnomeKeyboardsUtil.write_input_sources(sources) return '' From 82184d7f2b33b1430fb7e4ec0416a4662543c086 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 18:56:00 +0200 Subject: [PATCH 45/81] fix(linux): respect XDG_DATA_HOME environment variable --- linux/keyman-config/km-package-uninstall.bash-completion | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux/keyman-config/km-package-uninstall.bash-completion b/linux/keyman-config/km-package-uninstall.bash-completion index ff07a31580..451a2e3e80 100644 --- a/linux/keyman-config/km-package-uninstall.bash-completion +++ b/linux/keyman-config/km-package-uninstall.bash-completion @@ -23,11 +23,11 @@ _km-package-uninstall_completions() shared="" case "${prev}" in "-s"|"--shared") - for file in `ls -d /usr/local/share/keyman/*/`; do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done + for file in $(ls -1 /usr/local/share/keyman); do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done COMPREPLY=($(compgen -W "${shared}" -- ${cur})) ;; *) - for file in `ls -d ~/.local/share/keyman/*/`; do kbid="`basename ${file}`"; words="${words} ${kbid}"; done + for file in $(ls -1 ${XDG_DATA_HOME:-~/.local/share}/keyman); do kbid="`basename ${file}`"; words="${words} ${kbid}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) ;; esac From 173bd5c008e64b843d3208f8862f9114b51f9cac Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 24 Jun 2021 14:02:05 -0400 Subject: [PATCH 46/81] auto: increment master version to 15.0.75 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- .../web/input-processor/package-lock.json | 2 +- common/core/web/input-processor/package.json | 16 +++++++-------- .../web/keyboard-processor/package-lock.json | 2 +- .../core/web/keyboard-processor/package.json | 10 +++++----- .../core/web/tools/recorder/package-lock.json | 2 +- common/core/web/tools/recorder/package.json | 10 +++++----- .../tools/sentry-manager/package-lock.json | 2 +- .../web/tools/sentry-manager/package.json | 4 ++-- common/core/web/utils/package-lock.json | 2 +- common/core/web/utils/package.json | 6 +++--- common/models/templates/package-lock.json | 2 +- common/models/templates/package.json | 8 ++++---- common/models/types/package-lock.json | 2 +- common/models/types/package.json | 2 +- common/models/wordbreakers/package-lock.json | 2 +- common/models/wordbreakers/package.json | 4 ++-- common/predictive-text/package-lock.json | 2 +- common/predictive-text/package.json | 14 ++++++------- developer/js/package-lock.json | 2 +- developer/js/package.json | 8 ++++---- lerna.json | 2 +- resources/gosh/package-lock.json | 2 +- resources/gosh/package.json | 2 +- resources/web-environment/package-lock.json | 2 +- resources/web-environment/package.json | 4 ++-- web/package-lock.json | 2 +- web/package.json | 20 +++++++++---------- 29 files changed, 76 insertions(+), 69 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 5803417172..c8475b602c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 15.0.74 alpha 2021-06-24 + +* fix(android): fixes application of nextlayer for subkeys with customized layer setting (#5350) +* feat(common/core/web): engine correction-prep optimizations (#5319) +* feat(linux): Add support for impish (Ubuntu 21.10) (#5334) +* fix(linux): Add libglib2.0-bin to keyman Depends 🍒 (#5360) + ## 15.0.73 alpha 2021-06-23 * chore(windows): add engine test framework (#5337) diff --git a/VERSION.md b/VERSION.md index 8a872f0ae5..6deb3bdb5c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.74 \ No newline at end of file +15.0.75 \ No newline at end of file diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 70b2db0e37..0a70e1cb1b 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index cfaa8c6aab..bf9310b59c 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.74", + "version": "15.0.75", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.74", - "@keymanapp/resources-gosh": "^15.0.74", + "@keymanapp/lexical-model-compiler": "^15.0.75", + "@keymanapp/resources-gosh": "^15.0.75", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.74", - "@keymanapp/lexical-model-layer": "^15.0.74", - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74", + "@keymanapp/keyboard-processor": "^15.0.75", + "@keymanapp/lexical-model-layer": "^15.0.75", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 67bb79dc09..5bfe577747 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 9a16643887..941a50c72b 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.74", + "version": "15.0.75", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.74", + "@keymanapp/resources-gosh": "^15.0.75", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75", "@types/node": "^11.9.4" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index 159b0a5207..c16d867007 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 097741f4a1..81270ce523 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.74", + "version": "15.0.75", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.74", - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74" + "@keymanapp/keyboard-processor": "^15.0.75", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75" }, "devDependencies": { "typescript": "^3.8.3" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 40c7fe85ae..b8d1a7b528 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index 8759e651d4..68ffeb55e8 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.74", + "version": "15.0.75", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.74", + "@keymanapp/web-environment": "^15.0.75", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index ead1b8b557..4ade265155 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index dba9b46df2..8b8c9673fd 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.74", + "version": "15.0.75", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", + "@keymanapp/resources-gosh": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 9a4707aa72..7e46153c02 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index c05c906aba..e1618a4f72 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.74", + "version": "15.0.75", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.74" + "@keymanapp/models-wordbreakers": "^15.0.75" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index 25357704b4..b1b7079bac 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index 696cc652c1..1160ebf03b 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.74", + "version": "15.0.75", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index 0f00f148a3..a42e626aa7 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 096c5fd886..a6143c7546 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.74", + "version": "15.0.75", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.74", + "@keymanapp/models-types": "^15.0.75", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 1439ab5785..ec67e11eec 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index 933bd9ca36..857205f836 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.74", + "version": "15.0.75", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/resources-gosh": "^15.0.74", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/resources-gosh": "^15.0.75", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.2", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.74", - "@keymanapp/models-wordbreakers": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74", + "@keymanapp/models-templates": "^15.0.75", + "@keymanapp/models-wordbreakers": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index 3043776b92..4241733660 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index d92aabdcbd..006169292d 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.74", + "version": "15.0.75", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.74", + "@keymanapp/models-types": "^15.0.75", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.74", - "@keymanapp/models-wordbreakers": "^15.0.74", + "@keymanapp/models-templates": "^15.0.75", + "@keymanapp/models-wordbreakers": "^15.0.75", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/lerna.json b/lerna.json index 6d03b53a43..bc5c9735de 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.74" + "version": "15.0.75" } diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index b75a218b20..43fa05c510 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index f98534fb44..152c04841b 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.74", + "version": "15.0.75", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 2004c12035..7fcf7c70af 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index 2a771ffb97..e43f051033 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.74", + "version": "15.0.75", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.74" + "@keymanapp/resources-gosh": "^15.0.75" } } diff --git a/web/package-lock.json b/web/package-lock.json index dac9e4b03a..b9ac17af83 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.74", + "version": "15.0.75", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index 04a88af3ab..c38071f598 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.74", + "version": "15.0.75", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.74", - "@keymanapp/web-sentry-manager": "^15.0.74", + "@keymanapp/resources-gosh": "^15.0.75", + "@keymanapp/web-sentry-manager": "^15.0.75", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.74", - "@keymanapp/keyboard-processor": "^15.0.74", - "@keymanapp/lexical-model-layer": "^15.0.74", - "@keymanapp/models-types": "^15.0.74", - "@keymanapp/recorder-core": "^15.0.74", - "@keymanapp/web-environment": "^15.0.74", - "@keymanapp/web-utils": "^15.0.74", + "@keymanapp/input-processor": "^15.0.75", + "@keymanapp/keyboard-processor": "^15.0.75", + "@keymanapp/lexical-model-layer": "^15.0.75", + "@keymanapp/models-types": "^15.0.75", + "@keymanapp/recorder-core": "^15.0.75", + "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-utils": "^15.0.75", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" From c14443d79881acd5b4ca201fb1e5ce0ceedb2d25 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 25 Jun 2021 08:45:00 +0700 Subject: [PATCH 47/81] chore(web): Cleanup console logging --- web/source/keyboards/kmwkeyboards.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 443a3d1dce..84f80af2bb 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1242,7 +1242,6 @@ namespace com.keyman.keyboards { try { // Merge this with errorStub let result:(KeyboardStub|ErrorStub)[]|Error = await this.keymanCloudRequest('&keyboardid='+cmd, false); - console.log("Try to merge result and errorStub"); if (Array.isArray(result)) { if (errorStub.length > 0) { result = result.concat(errorStub); @@ -1253,7 +1252,7 @@ namespace com.keyman.keyboards { } } catch(err) { // We don't have language info for this ErrorStub - console.log("catch err"); + console.error(err); let stub: ErrorStub = {error: err}; errorStub.push(stub); return Promise.reject(errorStub); From b843a24f9ad78d95a6684d0ecf3efc9394b8580c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 25 Jun 2021 09:15:13 +0700 Subject: [PATCH 48/81] chore(web): Address additional review comments * Add more description on promise-api/index.html * Use OptionType --- web/source/dom/domManager.ts | 4 ++-- web/source/kmwbase.ts | 2 +- web/source/kmwembedded.ts | 2 +- web/testing/promise-api/index.html | 17 ++++++++++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 01d22c3e9c..eacc228c74 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1474,10 +1474,10 @@ namespace com.keyman.dom { /** * Function Initialization * Scope Public - * @param {Object} arg object array of user-defined properties + * @param {com.keyman.OptionType} arg object of user-defined properties * Description KMW window initialization */ - init: (arg:any) => Promise = function(this: DOMManager, arg): Promise { + init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { var i,j,c,e,p,eTextArea,eInput,opt,dTrailer,ds; var osk = this.keyman.osk; var util = this.keyman.util; diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index e121544b45..882922b376 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -101,7 +101,7 @@ namespace com.keyman { }; // Stub functions (defined later in code only if required) - setDefaultDeviceOptions(opt){} + setDefaultDeviceOptions(opt: OptionType){} getStyleSheetPath(s){return s;} getKeyboardPath(f, p?){return f;} KC_(n, ln, Pelem){return '';} diff --git a/web/source/kmwembedded.ts b/web/source/kmwembedded.ts index 40589a5063..6917539489 100644 --- a/web/source/kmwembedded.ts +++ b/web/source/kmwembedded.ts @@ -183,7 +183,7 @@ namespace com.keyman.text { com.keyman.osk.VisualKeyboard.prototype.popupDelay = 400; // Delay must be less than native touch-hold delay // Set default device options - keymanweb.setDefaultDeviceOptions = function(opt) { + keymanweb.setDefaultDeviceOptions = function(opt: com.keyman.OptionType) { opt['attachType'] = 'manual'; device.app=opt['app']; device.touchable=true; diff --git a/web/testing/promise-api/index.html b/web/testing/promise-api/index.html index 4160beae2c..3c9274877d 100644 --- a/web/testing/promise-api/index.html +++ b/web/testing/promise-api/index.html @@ -57,15 +57,26 @@

KeymanWeb Sample Page - Promise API

This page is for testing the Promises returned from adding keyboards. - See Specification #5044 + See Specification #5044. +

+

+ This test page is best used with Developer mode, as output for both successes + and failures will be logged to the JS console. +

+

Visible alert-style feedback will only occur for invalid search terms, such as using "foobar" + for any of the three keyboard-adding options at the bottom of the page.

-

You are currently testing under the alerts mode. +

You are currently testing under the internal alerts mode. Two alert modes are available: Disabled or Enabled (Default)

+

This will only affect whether or not KeymanWeb's alert modal is displayed. + A native browser alert and dev-console error log will occur regardless of this + setting when stub fetch errors occur. +

- From 972d6920b4ca31370130f87b65783b121ec1403f Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 25 Jun 2021 12:38:19 +0700 Subject: [PATCH 49/81] chore(web): Update languageListPromise type --- web/source/keyboards/kmwkeyboards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index d540c0c897..8b681b6763 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -135,7 +135,7 @@ namespace com.keyman.keyboards { */ registrationResolvers: {[timeoutID: number] : RegistrationPromiseTuple} = {}; - languageListPromise: Promise<(KeyboardStub)[]> = null; + languageListPromise: Promise<(KeyboardStub|ErrorStub)[]> = null; languageList: any[] = null; // List of keyboard languages available for KeymanCloud From 6fc7bade47899595cefdcfddb3fc37b583b4ad93 Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 25 Jun 2021 15:57:11 +1000 Subject: [PATCH 50/81] fix(windows): AppContext:Get uses BufMax to get maximium buffer size --- windows/src/engine/keyman32/appint/appint.cpp | 18 ++++++++ windows/src/engine/keyman32/appint/appint.h | 1 + .../keyman32-tests/keyman32-tests.vcxproj | 4 ++ .../keyman32/tests/keyman32-tests/test.cpp | 46 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index 6bf4a4deb3..ea6e877fe5 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -117,6 +117,24 @@ void AppContext::Get(WCHAR *buf, int bufsize) //buf[bufsize-1] = 0; } +void AppContext::GetSize(WCHAR* buf, int bufsize) +{ + // surrogate pairs need to be treated as a single unit. + // Buff Max handles the case where the surrogate pairs is split + // by the buff size + WCHAR* startIndex = this->BufMax(bufsize); + + for (WCHAR* p = startIndex; *p && bufsize > 0; p++, bufsize--) + { + *buf = *p; + if ((*p >= 0xD800 && *p <= 0xDBFF) && (bufsize - 2 > 0)) { buf++; *buf = *(++p); bufsize--; } + buf++; + } + + *buf = 0; + +} + void AppContext::CopyFrom(AppContext *source) // I3575 { SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 1)); diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 748eca5522..c63ccc1d83 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -92,6 +92,7 @@ public: void Delete(); void Reset(); void Get(WCHAR *buf, int bufsize); + void GetSize(WCHAR* buf, int bufsize); void Set(const WCHAR *buf); WCHAR *BufMax(int n); WCHAR *Buf(int n); diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj index ad0235d603..171ae81cd8 100644 --- a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj @@ -69,6 +69,9 @@ {a7069186-5ae7-4504-8926-861b7a34ba9e} + + {bd5564fb-35a5-4a3c-b96a-4a6578e2b593} + @@ -88,6 +91,7 @@ true Console + version.lib;psapi.lib;imm32.lib;%(AdditionalDependencies) diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp b/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp index a72be13e58..27fbc17585 100644 --- a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp +++ b/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp @@ -11,3 +11,49 @@ TEST(TestCaseName, TestName) { TEST(xstring, xstrlen) { EXPECT_EQ(3, xstrlen(L"abc")); } + +// Test Context should return bcd +TEST(AppContext, Get_small_buff) { + + WCHAR tmpbuf[MAXCONTEXT]; + AppContext myContext; + myContext.Set(L"abcd"); + myContext.Get(tmpbuf, 3); +// EXPECT_STREQ((L"bcd"), tmpbuf); +} + +// Test Context should return bcd +TEST(AppContext, GetSize_small_buff) { + + WCHAR tmpbuf[MAXCONTEXT]; + AppContext myContext; + myContext.Set(L"abcd"); + myContext.GetSize(tmpbuf, 3); + EXPECT_STREQ((L"bcd"), tmpbuf); +} + +// Test with the surrage pair in the middle of the size buffer requested +// Set the context with a string containing a surratage pair. + +TEST(AppContext, Get_over_run_buf) { + + WCHAR tmpbuf[MAXCONTEXT]; + AppContext myContext; + + // myContext.Set(L"a𐐷bc4"); + //myContext.GetSize(tmpbuf, 10); + + WCHAR testString[] = { 0x0042, 0x0042, 0xD800, 0xDC00, 0x0043, 0x0044, 0x0000 }; + WCHAR expectedString[] = { 0x0043, 0x0044, 0x0000 }; + // Test for breaking at surrogate and one less should give the same string. + myContext.Set(testString); + myContext.GetSize(tmpbuf, 10); + + EXPECT_STREQ((testString), tmpbuf); + // split the surrogate should return on less than requested + myContext.GetSize(tmpbuf, 3); + EXPECT_STREQ((expectedString), tmpbuf); + // This will should be the same size as the split surrogate + myContext.GetSize(tmpbuf, 2); + EXPECT_STREQ((expectedString), tmpbuf); +} From dfffe00853dd2b275e128ec2afe111e969fc4c8c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 25 Jun 2021 11:42:37 +0200 Subject: [PATCH 51/81] refactor(linux): remove some markdownlint warnings --- linux/help/about/history.md | 1 + linux/help/about/index.md | 2 +- linux/help/about/welcome.md | 3 ++- linux/help/about/whatsnew.md | 2 +- linux/help/common/index.md | 16 +++++++++++++--- linux/help/index.md | 5 ++++- linux/help/start/index.md | 2 +- linux/help/start/installing-keyboard.md | 2 ++ 8 files changed, 25 insertions(+), 8 deletions(-) diff --git a/linux/help/about/history.md b/linux/help/about/history.md index 506782a42f..6343e0313e 100644 --- a/linux/help/about/history.md +++ b/linux/help/about/history.md @@ -5,4 +5,5 @@ title: Version History A frequent updated changelog for Keyman is kept at [help.keyman.com/version-history](https://help.keyman.com/version-history) on the Keyman Help website. ## Related Topics + * [What's New](whatsnew) diff --git a/linux/help/about/index.md b/linux/help/about/index.md index dbc704bbf4..5064ed6a09 100644 --- a/linux/help/about/index.md +++ b/linux/help/about/index.md @@ -4,4 +4,4 @@ title: About Keyman * [Welcome to Keyman](welcome) * [What's New](whatsnew) -* [Version History](history) \ No newline at end of file +* [Version History](history) diff --git a/linux/help/about/welcome.md b/linux/help/about/welcome.md index 0608c4d10c..ecab5b7b29 100644 --- a/linux/help/about/welcome.md +++ b/linux/help/about/welcome.md @@ -5,8 +5,9 @@ title: Welcome to Keyman Thank you for installing Keyman for Linux. Whether you're a new or returning user, we believe you will appreciate the significant enhancements in this latest version of Keyman for Linux. ## What is Keyman for Linux? + Keyman for Linux makes it possible to type in over 1,000 languages in any Linux application. Keyman for Linux also supports KMFL keyboards, so your existing KMFL keyboards will continue to work. -[Click here](../start/) to get started. \ No newline at end of file +[Click here](../start/) to get started. diff --git a/linux/help/about/whatsnew.md b/linux/help/about/whatsnew.md index b4d235c018..0edd3e50ec 100644 --- a/linux/help/about/whatsnew.md +++ b/linux/help/about/whatsnew.md @@ -9,5 +9,5 @@ Here are some of the new features we have added to Keyman for Linux 14.0: * Improved user interface * Improved support for KDE, Gnome, Arch Linux -[![](../linux_images/video.png) Watch a video](https://youtu.be/4W9Z9_IFUII) +[![Watch a video](../linux_images/video.png)](https://youtu.be/4W9Z9_IFUII) that highlights some of these new features. diff --git a/linux/help/common/index.md b/linux/help/common/index.md index 9d8fd517c5..4d4f39ba05 100644 --- a/linux/help/common/index.md +++ b/linux/help/common/index.md @@ -3,7 +3,9 @@ title: Common Questions --- ## Q. How do I install Keyman for Linux? + **A.** In Ubuntu and Wasta-Linux, Keyman for Linux can be installed via launchpad: + ```bash sudo add-apt-repository ppa:keymanapp/keyman sudo apt-get update @@ -11,22 +13,25 @@ sudo apt-get upgrade sudo apt-get install keyman onboard-keyman ``` -An older version of Keyman for Linux packages is also available in any current Debian at: +An older version of Keyman for Linux packages is also available in any current Debian at: [https://packages.debian.org/keyman](https://packages.debian.org/keyman) - ## Q. How do I install a Keyman keyboard? + **A.** Follow this [guide](../start/installing-keyboard) for installing Keyman keyboards. ## Q. What's the relationship between Keyman for Linux and KMFL? + **A.** Keyman for Linux and KMFL are released together in the Keyman [PPA](https://en.wikipedia.org/wiki/Ubuntu#Package_Archives), but are separate. The installation instructions above will install Keyman, not KMFL. KMFL was developed as a black-box clone of Keyman -for Windows years ago; after Keyman was released as open source it became possible to share the +for Windows years ago; after Keyman was released as open source it became possible to share the code base between Windows and Linux. Keyman for Linux is the long-term supported solution. ## Q. Can I have Keyman for Linux and KMFL installed at the same time? + **A.** Yes. To install KMFL on Ubuntu: + ```bash sudo add-apt-repository ppa:keymanapp/keyman sudo apt-get update @@ -35,22 +40,27 @@ sudo apt-get install ibus-kmfl ``` ## Q. If I already had KMFL installed, how can I uninstall KMFL before installing Keyman? + **A.** It is good to remove any keyboards from ibus e.g. KMFL keyboards before you remove KMFL. Then, to remove KMFL: + ```bash sudo dpkg --purge ibus-kmfl libkmfl ``` ## Q. How do I display the On-screen Keyboard (OSK)? + **A.** From the launcher, enter `onboard` ![](../linux_images/onboard.png "Onboard") ## Q. What Linux distros will Keyman work with? + **A.** Keyman runs on Debian, Ubuntu, Wasta Linux and can be compiled to run from source in most distributions. **Note:** Keyman for Linux no longer supports Ubuntu 16.04 LTS (Xenial Xerus). ## Q. Will my existing Windows Keyman keyboard work with Keyman for Linux? + **A.** Most keyboards will work without change. A small subset of keyboards require features which are not yet available in Keyman for Linux. These features will be progressively implemented. diff --git a/linux/help/index.md b/linux/help/index.md index 5e52e8fe40..0abc777916 100644 --- a/linux/help/index.md +++ b/linux/help/index.md @@ -1,20 +1,23 @@ --- -title: Keyman for Linux 14.0 Help +title: Keyman for Linux 15.0 Help --- Need help using Keyman for Linux? In time, this product documentation will grow and explain frequently asked questions. ## [About Keyman for Linux](about/) + * [Welcome to Keyman](about/welcome) * [What's New](about/whatsnew) * [Version History](about/history) ## [Getting Started](start/) + * [Installing a keyboard](start/installing-keyboard) ## [Common Questions](common/) ## Ask for Help + [Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman) ## Help Topics diff --git a/linux/help/start/index.md b/linux/help/start/index.md index 2468dafa5a..b3aad00bd5 100644 --- a/linux/help/start/index.md +++ b/linux/help/start/index.md @@ -1,5 +1,5 @@ --- -title: Getting Started" +title: Getting Started --- * [How To - Installing Keyboards with Keyman for Linux](installing-keyboard) diff --git a/linux/help/start/installing-keyboard.md b/linux/help/start/installing-keyboard.md index f571d6f8b8..42e2ecbbaa 100644 --- a/linux/help/start/installing-keyboard.md +++ b/linux/help/start/installing-keyboard.md @@ -8,6 +8,7 @@ Alternatively from the command line run `km-config`. ![](../linux_images/km-config.png "Keyman Configuration") ## Download from Keyman cloud repository + From Keyman Configuration, click "Download". At the search page, enter the keyboard you want to download. ![](../linux_images/search-khmer-angkor.png "Search for khmer_angkor keyboard") @@ -17,6 +18,7 @@ Clicking the keyboard brings the keyboard page which has a link for installation ![](../linux_images/download-khmer-angkor.png "Install khmer_angkor") ## Install Local .kmp keyboard package + Keyman for Linux can also install keyboards from local .kmp keyboard packages. From Keyman Configuration, click "Install". Then browse to the local .kmp keyboard package and install. From a39bc9560756c68bbcb02f9abfad0b56a3d08d69 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 25 Jun 2021 11:43:28 +0200 Subject: [PATCH 52/81] feat(linux): Add reference index page Part of #4419. --- linux/help/reference/index.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 linux/help/reference/index.md diff --git a/linux/help/reference/index.md b/linux/help/reference/index.md new file mode 100644 index 0000000000..2c76ebfe36 --- /dev/null +++ b/linux/help/reference/index.md @@ -0,0 +1,25 @@ +--- +title: Keyman 15.0 for Linux Reference +--- + +## Keyman 15.0 for Linux Overview + +Keyman for Linux makes it possible for you to type in over 1,000 languages on Linux. + +[km-config](km-config) \ +Keyman keyboards installation and information + +[km-kvk2ldml](km-kvk2ldml) \ +Convert a Keyman on-screen keyboard file to LDML + +[km-package-get](km-package-get) \ +Download a Keyman keyboard package + +[km-package-install](km-package-install) \ +Install a Keyman keyboard package + +[km-package-list-installed](km-package-list-installed) \ +List installed Keyman keyboard packages + +[km-package-uninstall](km-package-uninstall) \ +Uninstall a Keyman keyboard package From 76d2f65abc22f53f27c387876248bf04251cf319 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 18:39:56 +0200 Subject: [PATCH 53/81] refactor(linux): Use single quotes --- linux/keyman-config/keyman_config/get_kmp.py | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index bf5eb00a32..bccebb6547 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -19,9 +19,9 @@ def get_package_download_data(packageID, weekCache=False): Returns: dict: Keyboard data """ - logging.info("Getting download data for package %s", packageID) - api_url = KeymanDownloadsUrl + "/api/keyboard/1.0/" + packageID - logging.debug("At URL %s", api_url) + logging.info('Getting download data for package %s', packageID) + api_url = KeymanDownloadsUrl + '/api/keyboard/1.0/' + packageID + logging.debug('At URL %s', api_url) cache_dir = keyman_cache_dir() current_dir = os.getcwd() if weekCache: @@ -32,7 +32,7 @@ def get_package_download_data(packageID, weekCache=False): requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) response = requests.get(api_url) - logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) + logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: @@ -51,9 +51,9 @@ def get_keyboard_data(keyboardID, weekCache=False): Returns: dict: Keyboard data """ - logging.info("Getting data for keyboard %s", keyboardID) - api_url = KeymanApiUrl + "/keyboard/" + keyboardID - logging.debug("At URL %s", api_url) + logging.info('Getting data for keyboard %s', keyboardID) + api_url = KeymanApiUrl + '/keyboard/' + keyboardID + logging.debug('At URL %s', api_url) cache_dir = keyman_cache_dir() current_dir = os.getcwd() if weekCache: @@ -64,7 +64,7 @@ def get_keyboard_data(keyboardID, weekCache=False): requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after) now = time.ctime(int(time.time())) response = requests.get(api_url) - logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) + logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() if response.status_code == 200: @@ -91,9 +91,9 @@ def keyman_cache_dir(): Returns: str: path of user keyman cache folder """ - home = os.path.expanduser("~") - cachebase = os.environ.get("XDG_CACHE_HOME", os.path.join(home, ".cache")) - km_cache = os.path.join(cachebase, "keyman") + home = os.path.expanduser('~') + cachebase = os.environ.get('XDG_CACHE_HOME', os.path.join(home, '.cache')) + km_cache = os.path.join(cachebase, 'keyman') if not os.path.isdir(km_cache): os.mkdir(km_cache) return km_cache @@ -126,7 +126,7 @@ def get_kmp_file(downloaddata, cache=False): str: path where kmp file has been downloaded """ if 'kmp' not in downloaddata: - logging.info("get_kmp.py: Package does not have a kmp file available") + logging.info('get_kmp.py: Package does not have a kmp file available') return None downloadfile = os.path.join(get_download_folder(), os.path.basename(downloaddata['kmp'])) @@ -146,7 +146,7 @@ def download_kmp_file(url, kmpfile, cache=False): Returns: str: path where kmp file has been downloaded """ - logging.info("Download URL: %s", url) + logging.info('Download URL: %s', url) downloadfile = None if cache: @@ -162,11 +162,11 @@ def download_kmp_file(url, kmpfile, cache=False): try: response = requests.get(url) # , stream=True) except requests.exceptions.ConnectionError: - logging.error("Connection error downloading %s", url) + logging.error('Connection error downloading %s', url) return downloadfile if cache: - logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache)) + logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache)) os.chdir(current_dir) requests_cache.core.uninstall_cache() @@ -190,6 +190,6 @@ def get_kmp(packageID): if (downloaddata): return get_kmp_file(downloaddata) else: - logging.warning("get_kmp.py: Could not get download information about keyboard package.") + logging.warning('get_kmp.py: Could not get download information about keyboard package.') return None return From 5bde5b5904abc30506e56865cd58fd88a97942aa Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 19:00:56 +0200 Subject: [PATCH 54/81] chore(linux): Add `deprecated` decorator --- .../keyman_config/deprecated_decorator.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 linux/keyman-config/keyman_config/deprecated_decorator.py diff --git a/linux/keyman-config/keyman_config/deprecated_decorator.py b/linux/keyman-config/keyman_config/deprecated_decorator.py new file mode 100644 index 0000000000..9b9e1383a1 --- /dev/null +++ b/linux/keyman-config/keyman_config/deprecated_decorator.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +# based on https://stackoverflow.com/a/40301488 +import logging + + +string_types = (type(b''), type(u'')) + + +def deprecated(reason): + if isinstance(reason, string_types): + # The @deprecated is used with a 'reason'. + def decorator(func1): + def new_func1(*args, **kwargs): + logging.warning("Call to deprecated function '{name}': {reason}.".format( + name=func1.__name__, reason=reason)) + return func1(*args, **kwargs) + return new_func1 + return decorator + else: + # The @deprecated is used without any 'reason'. + def new_func2(*args, **kwargs): + func2 = reason + logging.warning("Call to deprecated function '{name}'.".format(name=func2.__name__)) + return func2(*args, **kwargs) + return new_func2 From 7c28e87eced79c006273438041421d484f1920c8 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 19:02:30 +0200 Subject: [PATCH 55/81] fix(linux): fix crash in `km-package-install -p foo` --- linux/keyman-config/keyman_config/install_kmp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index 8f498f9e73..5e1199cffb 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -51,7 +51,7 @@ class InstallKmp(): self.kmpdocdir = '' self.kmpfontdir = '' - def _check_keyman_dir(basedir, error_message): + def _check_keyman_dir(self, basedir, error_message): # check if keyman subdir exists keyman_dir = os.path.join(basedir, "keyman") if os.path.isdir(keyman_dir): From 0a2dd29e53473b1892e0bc167d850e327814964e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 25 Jun 2021 18:49:41 +0200 Subject: [PATCH 56/81] fix(linux): Fix restarting ibus when running with `sudo` When installing in the shared area the user has to run `km-package-install` with sudo so that he has sufficient permissions. In that case we have to do extra work to restart ibus for the logged-in user instead of root. Fixes #5309. --- .../keyman-config/keyman_config/ibus_util.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/linux/keyman-config/keyman_config/ibus_util.py b/linux/keyman-config/keyman_config/ibus_util.py index ae25ec5212..464ea116bd 100644 --- a/linux/keyman-config/keyman_config/ibus_util.py +++ b/linux/keyman-config/keyman_config/ibus_util.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 +import os import time import gi import logging @@ -68,15 +69,21 @@ def restart_ibus_subp(): def restart_ibus(bus=None): - try: - if not bus: - bus = get_ibus_bus() - logging.info("restarting IBus") - bus.exit(True) - bus.destroy() - except Exception as e: - logging.warning("Failed to restart IBus") - logging.warning(e) + realuser = os.environ.get('SUDO_USER') + if realuser: + # we have been called with `sudo`. Restart ibus for the real user. + logging.info('restarting IBus by subprocess for user %s', realuser) + subprocess.run(['sudo', '-u', realuser, 'ibus', 'restart']) + else: + try: + if not bus: + bus = get_ibus_bus() + logging.info("restarting IBus") + bus.exit(True) + bus.destroy() + except Exception as e: + logging.warning("Failed to restart IBus") + logging.warning(e) def bus_has_engine(bus, name): From 35d506c977cf2aba9107c5c5ab0b6efa1fe2eff3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 24 Jun 2021 19:07:41 +0200 Subject: [PATCH 57/81] refactor(linux): Centralize paths Introduce methods to get paths instead of hard-coding the values. This also deprecates four methods (`user_keyman_dir`, `user_keyman_font_dir`, `user_keyboard_dir`, and `get_isntall_area_path`). This change also fixes a crash trying to install a package in the shared area. --- linux/keyman-config/keyman_config/get_kmp.py | 49 ++++++++++++++--- .../keyman_config/install_kmp.py | 17 +++--- .../keyman_config/install_window.py | 13 ++--- .../keyman_config/list_installed_kmp.py | 53 +++++-------------- .../keyman_config/uninstall_kmp.py | 21 +++++--- .../keyman_config/view_installed.py | 30 +++++------ linux/keyman-config/km-package-list-installed | 15 +++--- 7 files changed, 106 insertions(+), 92 deletions(-) diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index bccebb6547..9d5665aaa1 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -6,7 +6,16 @@ import logging import requests import requests_cache import os +from gi.repository import GObject from keyman_config import KeymanApiUrl, KeymanDownloadsUrl +from keyman_config.deprecated_decorator import deprecated + + +class InstallLocation(GObject.GEnum): + OS = 1 + Shared = 2 + User = 3 + Unknown = 99 def get_package_download_data(packageID, weekCache=False): @@ -99,20 +108,46 @@ def keyman_cache_dir(): return km_cache +def _get_data_base_dir(area): + if area == InstallLocation.User: + return os.environ.get('XDG_DATA_HOME', + os.path.join(os.path.expanduser('~'), '.local', 'share')) + if area == InstallLocation.Shared: + return '/usr/local/share' + return '/usr/share' + + +@deprecated('Use get_keyman_dir(InstallLocation.User) instead') def user_keyman_dir(): - home = os.path.expanduser("~") - datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share")) - return os.path.join(datahome, "keyman") + return get_keyman_dir(InstallLocation.User) +def get_keyman_dir(area): + return os.path.join(_get_data_base_dir(area), 'keyman') + + +@deprecated('Use get_keyman_font_dir(InstallLocation.User) instead') def user_keyman_font_dir(): - home = os.path.expanduser("~") - datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share")) - return os.path.join(datahome, "fonts", "keyman") + return os.path.join(_get_data_base_dir(InstallLocation.User), 'fonts', 'keyman') +def get_keyman_font_dir(area, keyboardid): + return os.path.join(_get_data_base_dir(area), 'fonts', 'keyman', keyboardid) + + +@deprecated('Use get_keyboard_dir(InstallLocation.User, keyboardid) instead') def user_keyboard_dir(keyboardid): - return os.path.join(user_keyman_dir(), keyboardid) + return get_keyboard_dir(InstallLocation.User, keyboardid) + + +def get_keyboard_dir(area, keyboardid): + return os.path.join(_get_data_base_dir(area), 'keyman', keyboardid) + + +def get_keyman_doc_dir(area, keyboardid): + if area == InstallLocation.User: + return get_keyboard_dir(area, keyboardid) + return os.path.join(_get_data_base_dir(area), 'doc', 'keyman') def get_kmp_file(downloaddata, cache=False): diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index 8f498f9e73..e346d3fa8a 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -10,7 +10,8 @@ from enum import Enum from keyman_config import _ from keyman_config.canonical_language_code_utils import CanonicalLanguageCodeUtils from keyman_config.fcitx_util import is_fcitx_running, restart_fcitx -from keyman_config.get_kmp import get_keyboard_data, user_keyboard_dir, user_keyman_font_dir +from keyman_config.get_kmp import get_keyboard_data, get_keyboard_dir, get_keyman_doc_dir +from keyman_config.get_kmp import get_keyman_font_dir, InstallLocation from keyman_config.kmpmetadata import get_metadata, KMFileTypes from keyman_config.convertico import extractico, checkandsaveico from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml @@ -89,17 +90,17 @@ class InstallKmp(): _("You do not have permissions to install the font files to the shared font area " "/usr/local/share/fonts")) - return self._install_kmp(inputfile, online, language) + return self._install_kmp(inputfile, online, language, InstallLocation.Shared) def install_kmp_user(self, inputfile, online=False, language=None): + return self._install_kmp(inputfile, online, language, InstallLocation.User) + + def _install_kmp(self, inputfile, online, language, area): self.packageID = self._extract_package_id(inputfile) - self.packageDir = user_keyboard_dir(self.packageID) - self.kmpdocdir = self.packageDir - self.kmpfontdir = os.path.join(user_keyman_font_dir(), self.packageID) + self.packageDir = get_keyboard_dir(area, self.packageID) + self.kmpdocdir = get_keyman_doc_dir(area, self.packageID) + self.kmpfontdir = get_keyman_font_dir(area, self.packageID) - return self._install_kmp(inputfile, online, language) - - def _install_kmp(self, inputfile, online, language): if not os.path.isdir(self.packageDir): os.makedirs(self.packageDir) diff --git a/linux/keyman-config/keyman_config/install_window.py b/linux/keyman-config/keyman_config/install_window.py index 820253b6c0..76482b022c 100755 --- a/linux/keyman-config/keyman_config/install_window.py +++ b/linux/keyman-config/keyman_config/install_window.py @@ -11,26 +11,26 @@ import webbrowser import tempfile import gi -from keyman_config.fcitx_util import is_fcitx_running gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, WebKit2 from distutils.version import StrictVersion from keyman_config import _ +from keyman_config.fcitx_util import is_fcitx_running from keyman_config.install_kmp import install_kmp, extract_kmp, get_metadata, InstallError, InstallStatus from keyman_config.list_installed_kmp import get_kmp_version from keyman_config.kmpmetadata import get_fonts from keyman_config.welcome import WelcomeView from keyman_config.uninstall_kmp import uninstall_kmp -from keyman_config.get_kmp import user_keyboard_dir +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir from keyman_config.accelerators import bind_accelerator, init_accel def find_keyman_image(image_file): - img_path = os.path.join("/usr/share/keyman/icons", image_file) + img_path = os.path.join(get_keyman_dir(InstallLocation.OS), "icons", image_file) if not os.path.isfile(img_path): - img_path = os.path.join("/usr/local/share/keyman/icons/", image_file) + img_path = os.path.join(get_keyman_dir(InstallLocation.Shared), "icons", image_file) if not os.path.isfile(img_path): img_path = os.path.join("keyman_config/icons/", image_file) if not os.path.isfile(img_path): @@ -302,7 +302,7 @@ class InstallKmpWindow(Gtk.Dialog): result = install_kmp(self.kmpfile, self.online, language=self.language) if result: # If install_kmp returns a string, it is an instruction for the end user, - # because for fcitx they will need to take extra steps to complete + # because for fcitx they will need to take extra steps to complete # installation themselves. dialog = Gtk.MessageDialog( self, 0, Gtk.MessageType.INFO, @@ -315,7 +315,8 @@ class InstallKmpWindow(Gtk.Dialog): if self.viewwindow: self.viewwindow.refresh_installed_kmp() keyboardid = os.path.basename(os.path.splitext(self.kmpfile)[0]) - welcome_file = os.path.join(user_keyboard_dir(keyboardid), "welcome.htm") + welcome_file = os.path.join(get_keyboard_dir(InstallLocation.User, keyboardid), + "welcome.htm") if os.path.isfile(welcome_file): uri_path = pathlib.Path(welcome_file).as_uri() logging.debug(uri_path) diff --git a/linux/keyman-config/keyman_config/list_installed_kmp.py b/linux/keyman-config/keyman_config/list_installed_kmp.py index 12177a5e60..1745a10dba 100755 --- a/linux/keyman-config/keyman_config/list_installed_kmp.py +++ b/linux/keyman-config/keyman_config/list_installed_kmp.py @@ -2,41 +2,14 @@ import os import json -from gi.repository import GObject from keyman_config.kmpmetadata import parsemetadata, parseinfdata -from keyman_config.get_kmp import user_keyman_dir - - -class InstallArea(GObject.GEnum): - IA_OS = 1 - IA_SHARED = 2 - IA_USER = 3 - IA_UNKNOWN = 99 +from keyman_config.get_kmp import get_keyman_dir, InstallLocation +from keyman_config.deprecated_decorator import deprecated +@deprecated('Use get_keyman_dir(area) instead') def get_install_area_path(area): - """ - Get the path of an install area. - - Args: - area (InstallArea): install area to check - InstallArea.IA_USER: ~/.local/share/keyman - InstallArea.IA_SHARED: /usr/local/share/keyman - InstallArea.IA_OS: /usr/share/keyman - InstallArea.IA_UNKNOWN: /usr/share/keyman - - Returns: - string: path of the install area - """ - check_path = "/usr/share/keyman" - if area == InstallArea.IA_USER: - check_path = user_keyman_dir() - elif area == InstallArea.IA_SHARED: - check_path = "/usr/local/share/keyman" - elif area == InstallArea.IA_OS: - check_path = "/usr/share/keyman" - - return check_path + return get_keyman_dir(area) def get_installed_kmp(area): @@ -44,10 +17,10 @@ def get_installed_kmp(area): Get list of installed kmp in an install area. Args: - area (InstallArea): install area to check - InstallArea.IA_USER: ~/.local/share/keyman - InstallArea.IA_SHARED: /usr/local/share/keyman - InstallArea.IA_OS: /usr/share/keyman + area (InstallLocation): install area to check + InstallLocation.User: ~/.local/share/keyman + InstallLocation.Shared: /usr/local/share/keyman + InstallLocation.OS: /usr/share/keyman Returns: list: Installed kmp dict: Keyboard @@ -59,7 +32,7 @@ def get_installed_kmp(area): path (str): base path where keyboard is installed description (str): Keyboard description """ - check_paths = [get_install_area_path(area)] + check_paths = [get_keyman_dir(area)] return get_installed_kmp_paths(check_paths) @@ -143,9 +116,9 @@ def get_kmp_version(packageID): None: if not found """ version = None - user_kmp = get_installed_kmp(InstallArea.IA_USER) - shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) - os_kmp = get_installed_kmp(InstallArea.IA_OS) + user_kmp = get_installed_kmp(InstallLocation.User) + shared_kmp = get_installed_kmp(InstallLocation.Shared) + os_kmp = get_installed_kmp(InstallLocation.OS) if packageID in os_kmp: version = os_kmp[packageID]['version'] @@ -180,7 +153,7 @@ def get_kmp_version_user(packageID): str: kmp version if kmp ID is installed None: if not found """ - user_kmp = get_installed_kmp(InstallArea.IA_USER) + user_kmp = get_installed_kmp(InstallLocation.User) if packageID in user_kmp: return user_kmp[packageID]['version'] else: diff --git a/linux/keyman-config/keyman_config/uninstall_kmp.py b/linux/keyman-config/keyman_config/uninstall_kmp.py index 68d07594e2..0c548c211f 100755 --- a/linux/keyman-config/keyman_config/uninstall_kmp.py +++ b/linux/keyman-config/keyman_config/uninstall_kmp.py @@ -4,7 +4,8 @@ import logging import os.path from shutil import rmtree -from keyman_config.get_kmp import user_keyboard_dir, user_keyman_font_dir +from keyman_config.fcitx_util import is_fcitx_running +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_doc_dir, get_keyman_font_dir from keyman_config.kmpmetadata import get_metadata from keyman_config.ibus_util import uninstall_from_ibus, get_ibus_bus, restart_ibus from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, get_ibus_keyboard_id, is_gnome_shell @@ -17,13 +18,13 @@ def uninstall_kmp_shared(packageID): Args: packageID (str): Keyboard package ID """ - kbdir = os.path.join('/usr/local/share/keyman', packageID) + kbdir = get_keyboard_dir(InstallLocation.Shared, packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) - kbdocdir = os.path.join('/usr/local/share/doc/keyman', packageID) - kbfontdir = os.path.join('/usr/local/share/fonts/keyman', packageID) + kbdocdir = get_keyman_doc_dir(InstallLocation.Shared, packageID) + kbfontdir = get_keyman_font_dir(InstallLocation.Shared, packageID) logging.info("Uninstalling shared keyboard: %s", packageID) if not os.access(kbdir, os.X_OK | os.W_OK): # Check for write access of keyman dir @@ -99,6 +100,10 @@ def uninstall_keyboards_from_gnome(keyboards, packageDir): gnomeKeyboardsUtil.write_input_sources(sources) +def _uninstall_keyboards_from_fcitx5(): + return _('Please use fcitx5-configtool to remove the keyboard from the group') + + def uninstall_kmp_user(packageID): """ Uninstall a kmp from ~/.local/share/keyman @@ -106,14 +111,16 @@ def uninstall_kmp_user(packageID): Args: packageID (str): Keyboard package ID """ - kbdir = user_keyboard_dir(packageID) + kbdir = get_keyboard_dir(InstallLocation.User, packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) logging.info("Uninstalling local keyboard: %s", packageID) info, system, options, keyboards, files = get_metadata(kbdir) if keyboards: - if is_gnome_shell(): + if is_fcitx_running(): + _uninstall_keyboards_from_fcitx5() + elif is_gnome_shell(): uninstall_keyboards_from_gnome(keyboards, kbdir) else: uninstall_keyboards_from_ibus(keyboards, kbdir) @@ -121,7 +128,7 @@ def uninstall_kmp_user(packageID): logging.warning("could not uninstall keyboards") rmtree(kbdir) logging.info("Removed user keyman directory: %s", kbdir) - fontdir = os.path.join(user_keyman_font_dir(), packageID) + fontdir = get_keyman_font_dir(InstallLocation.User, packageID) if os.path.isdir(fontdir): rmtree(fontdir) logging.info("Removed user keyman font directory: %s", fontdir) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 6cf9db84f6..d0a2240381 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -7,13 +7,14 @@ import subprocess import sys import gi + gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') from gi.repository import Gtk, GdkPixbuf from keyman_config import _ -from keyman_config.list_installed_kmp import get_install_area_path, get_installed_kmp, InstallArea +from keyman_config.list_installed_kmp import get_installed_kmp from keyman_config.welcome import WelcomeView from keyman_config.options import OptionsView from keyman_config.keyboard_details import KeyboardDetailsView @@ -21,7 +22,7 @@ from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.install_window import InstallKmpWindow, find_keyman_image from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.accelerators import bind_accelerator, init_accel -from keyman_config.get_kmp import user_keyboard_dir +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir class ViewInstalledWindowBase(Gtk.Window): @@ -122,7 +123,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # name str, # version str, # packageID - int, # enum InstallArea (KmpArea is GObject version) + int, # enum InstallLocation (KmpArea is GObject version) str, # path to welcome file if it exists or None str) # path to options file if it exists or None @@ -219,12 +220,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): kmpdata = installed_kmp[kmp] bmppng = ".bmp.png" # Icon file extension - if install_area == InstallArea.IA_USER: - path = user_keyboard_dir(kmpdata['packageID']) - elif install_area == InstallArea.IA_SHARED: - path = os.path.join("/usr/local/share/keyman", kmpdata['packageID']) - else: - path = os.path.join("/usr/share/keyman", kmpdata['packageID']) + path = get_keyboard_dir(install_area, kmpdata['packageID']) welcome_file = os.path.join(path, "welcome.htm") options_file = os.path.join(path, "options.htm") @@ -252,24 +248,24 @@ class ViewInstalledWindow(ViewInstalledWindowBase): logging.debug("Refreshing listview") self.store.clear() self.incomplete_kmp = [] - user_kmp = get_installed_kmp(InstallArea.IA_USER) + user_kmp = get_installed_kmp(InstallLocation.User) for kmp in sorted(user_kmp): kmpdata = user_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(user_kmp, self.store, InstallArea.IA_USER) - shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) + self.addlistitems(user_kmp, self.store, InstallLocation.User) + shared_kmp = get_installed_kmp(InstallLocation.Shared) for kmp in sorted(shared_kmp): kmpdata = shared_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(shared_kmp, self.store, InstallArea.IA_SHARED) - os_kmp = get_installed_kmp(InstallArea.IA_OS) + self.addlistitems(shared_kmp, self.store, InstallLocation.Shared) + os_kmp = get_installed_kmp(InstallLocation.OS) for kmp in sorted(os_kmp): kmpdata = os_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(os_kmp, self.store, InstallArea.IA_OS) + self.addlistitems(os_kmp, self.store, InstallLocation.OS) def on_tree_selection_changed(self, selection): model, treeiter = selection.get_selected() @@ -284,7 +280,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): _("Settings for keyboard {package}").format(package=model[treeiter][1])) logging.debug("You selected %s version %s", model[treeiter][1], model[treeiter][2]) self.about_button.set_sensitive(True) - if model[treeiter][4] == InstallArea.IA_USER: + if model[treeiter][4] == InstallLocation.User: logging.debug("Enabling uninstall button for %s in %s", model[treeiter][3], model[treeiter][4]) self.uninstall_button.set_sensitive(True) else: @@ -365,7 +361,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None and model[treeiter] is not None: logging.info("Show keyboard details of " + model[treeiter][1]) - areapath = get_install_area_path(model[treeiter][4]) + areapath = get_keyman_dir(model[treeiter][4]) kmp = { "name": model[treeiter][1], "version": model[treeiter][2], "packageID": model[treeiter][3], "areapath": areapath diff --git a/linux/keyman-config/km-package-list-installed b/linux/keyman-config/km-package-list-installed index fd7baa5e32..4f991ef22e 100755 --- a/linux/keyman-config/km-package-list-installed +++ b/linux/keyman-config/km-package-list-installed @@ -25,26 +25,27 @@ def main(): logging.basicConfig(format='%(levelname)s:%(message)s') all = not args.user and not args.shared and not args.os - from keyman_config.list_installed_kmp import get_installed_kmp, get_install_area_path, InstallArea + from keyman_config.list_installed_kmp import get_installed_kmp + from keyman_config.get_kmp import get_keyman_dir, InstallLocation if args.user or all: - installed_kmp = get_installed_kmp(InstallArea.IA_USER) + installed_kmp = get_installed_kmp(InstallLocation.User) if args.verbose or args.veryverbose: - print("--- Installed user Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_USER))) + print("--- Installed user Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.User))) else: print("--- Installed user Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) if args.shared or all: - installed_kmp = get_installed_kmp(InstallArea.IA_SHARED) + installed_kmp = get_installed_kmp(InstallLocation.Shared) if args.verbose or args.veryverbose: print("--- Installed shared Keyman keyboard packages (%s) ---" % - (get_install_area_path(InstallArea.IA_SHARED))) + (get_keyman_dir(InstallLocation.Shared))) else: print("--- Installed shared Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) if args.os or all: - installed_kmp = get_installed_kmp(InstallArea.IA_OS) + installed_kmp = get_installed_kmp(InstallLocation.OS) if args.verbose or args.veryverbose: - print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_OS))) + print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.OS))) else: print("--- Installed OS Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) From 50d19372bc5f26f7dbe80562204dd26f4e5b088a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 25 Jun 2021 19:39:16 +0200 Subject: [PATCH 58/81] refactor(linux): Use `path` instead of `os.path` --- .../keyman_config/keyboard_details.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/linux/keyman-config/keyman_config/keyboard_details.py b/linux/keyman-config/keyman_config/keyboard_details.py index 24945cb8f5..cf846c2d59 100644 --- a/linux/keyman-config/keyman_config/keyboard_details.py +++ b/linux/keyman-config/keyman_config/keyboard_details.py @@ -3,7 +3,7 @@ # Keyboard details window import json -import os.path +from os import path import qrcode import gi @@ -40,8 +40,8 @@ class KeyboardDetailsView(Gtk.Dialog): self.set_border_width(6) - packageDir = os.path.join(kmp['areapath'], kmp['packageID']) - kmp_json = os.path.join(packageDir, "kmp.json") + packageDir = path.join(kmp['areapath'], kmp['packageID']) + kmp_json = path.join(packageDir, "kmp.json") info, system, options, keyboards, files = parsemetadata(kmp_json) if info is None: @@ -58,15 +58,15 @@ class KeyboardDetailsView(Gtk.Dialog): return kbdata = None - jsonfile = os.path.join(packageDir, kmp['packageID'] + ".json") - if os.path.isfile(jsonfile): + jsonfile = path.join(packageDir, kmp['packageID'] + ".json") + if path.isfile(jsonfile): with open(jsonfile, "r") as read_file: kbdata = json.load(read_file) grid = Gtk.Grid() # grid.set_column_homogeneous(True) - # kbdatapath = os.path.join("/usr/local/share/keyman", kmp["id"], kmp["id"] + ".json") + # kbdatapath = path.join("/usr/local/share/keyman", kmp["id"], kmp["id"] + ".json") # Package info @@ -162,8 +162,8 @@ class KeyboardDetailsView(Gtk.Dialog): if keyboards: for kbd in keyboards: kbdata = None - jsonfile = os.path.join(packageDir, kbd['id'] + ".json") - if os.path.isfile(jsonfile): + jsonfile = path.join(packageDir, kbd['id'] + ".json") + if path.isfile(jsonfile): with open(jsonfile, "r") as read_file: kbdata = json.load(read_file) @@ -182,7 +182,7 @@ class KeyboardDetailsView(Gtk.Dialog): grid.attach_next_to(lbl_kbd_file, prevlabel, Gtk.PositionType.BOTTOM, 1, 1) prevlabel = lbl_kbd_file label = Gtk.Label() - label.set_text(os.path.join(packageDir, kbd['id'] + ".kmx")) + label.set_text(path.join(packageDir, kbd['id'] + ".kmx")) label.set_halign(Gtk.Align.START) label.set_selectable(True) grid.attach_next_to(label, lbl_kbd_file, Gtk.PositionType.RIGHT, 1, 1) @@ -291,8 +291,8 @@ class KeyboardDetailsView(Gtk.Dialog): # prevlabel = label8 # #TODO need to know which area keyboard is installed in to show this # # label = Gtk.Label() - # # welcome_file = os.path.join("/usr/local/share/doc/keyman", kmp["id"], "welcome.htm") - # # if os.path.isfile(welcome_file): + # # welcome_file = path.join("/usr/local/share/doc/keyman", kmp["id"], "welcome.htm") + # # if path.isfile(welcome_file): # # label.set_text(_("Installed")) # # else: # # label.set_text(_("Not installed")) @@ -325,7 +325,7 @@ class KeyboardDetailsView(Gtk.Dialog): # If it doesn't exist, generate QR code to share keyboard package path_qr = packageDir + "/qrcode.png" url = KeymanComUrl + "/go/keyboard/" + kmp['packageID'] + "/share" - if not os.path.isfile(path_qr): + if not path.isfile(path_qr): qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_H, From 768de5f66bf4c9bee27490dbc84429f39e87bdf9 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 25 Jun 2021 19:40:04 +0200 Subject: [PATCH 59/81] fix(linux): Save QR code in temporary directory Fixes #5310. --- linux/keyman-config/keyman_config/keyboard_details.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linux/keyman-config/keyman_config/keyboard_details.py b/linux/keyman-config/keyman_config/keyboard_details.py index cf846c2d59..1c7d4f1253 100644 --- a/linux/keyman-config/keyman_config/keyboard_details.py +++ b/linux/keyman-config/keyman_config/keyboard_details.py @@ -5,6 +5,7 @@ import json from os import path import qrcode +import tempfile import gi from gi.repository import Gtk @@ -323,7 +324,7 @@ class KeyboardDetailsView(Gtk.Dialog): prevlabel = lbl_pad # If it doesn't exist, generate QR code to share keyboard package - path_qr = packageDir + "/qrcode.png" + path_qr = path.join(tempfile.gettempdir(), kmp['packageID'] + '_qrcode.png') url = KeymanComUrl + "/go/keyboard/" + kmp['packageID'] + "/share" if not path.isfile(path_qr): qr = qrcode.QRCode( From ca69eae01198b1b35f05213b3c32418888fac211 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 25 Jun 2021 14:02:29 -0400 Subject: [PATCH 60/81] auto: increment master version to 15.0.76 --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- .../web/input-processor/package-lock.json | 2 +- common/core/web/input-processor/package.json | 16 +++++++-------- .../web/keyboard-processor/package-lock.json | 2 +- .../core/web/keyboard-processor/package.json | 10 +++++----- .../core/web/tools/recorder/package-lock.json | 2 +- common/core/web/tools/recorder/package.json | 10 +++++----- .../tools/sentry-manager/package-lock.json | 2 +- .../web/tools/sentry-manager/package.json | 4 ++-- common/core/web/utils/package-lock.json | 2 +- common/core/web/utils/package.json | 6 +++--- common/models/templates/package-lock.json | 2 +- common/models/templates/package.json | 8 ++++---- common/models/types/package-lock.json | 2 +- common/models/types/package.json | 2 +- common/models/wordbreakers/package-lock.json | 2 +- common/models/wordbreakers/package.json | 4 ++-- common/predictive-text/package-lock.json | 2 +- common/predictive-text/package.json | 14 ++++++------- developer/js/package-lock.json | 2 +- developer/js/package.json | 8 ++++---- lerna.json | 2 +- resources/gosh/package-lock.json | 2 +- resources/gosh/package.json | 2 +- resources/web-environment/package-lock.json | 2 +- resources/web-environment/package.json | 4 ++-- web/package-lock.json | 2 +- web/package.json | 20 +++++++++---------- 29 files changed, 78 insertions(+), 69 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c8475b602c..e4add6eb70 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 15.0.75 alpha 2021-06-25 + +* fix(android): rotation is not updating keyboard (#5247) +* fix(windows): osk scaling mismatch on horz axis (#5341) +* fix(linux): Don't add duplicate entries when reinstalling keyboard (#5363) +* fix(linux): fix tab completion for command line tools (#5362) +* feat(web): Update addLanguageKeyboards to return a Promise (#5260) +* feat(web): Add option useAlerts to control alerts() (#5302) + ## 15.0.74 alpha 2021-06-24 * fix(android): fixes application of nextlayer for subkeys with customized layer setting (#5350) diff --git a/VERSION.md b/VERSION.md index 6deb3bdb5c..9052ffd952 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.75 \ No newline at end of file +15.0.76 \ No newline at end of file diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 0a70e1cb1b..7217b2c075 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index bf9310b59c..89dd3d1c55 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.75", + "version": "15.0.76", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.75", - "@keymanapp/resources-gosh": "^15.0.75", + "@keymanapp/lexical-model-compiler": "^15.0.76", + "@keymanapp/resources-gosh": "^15.0.76", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.75", - "@keymanapp/lexical-model-layer": "^15.0.75", - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75", + "@keymanapp/keyboard-processor": "^15.0.76", + "@keymanapp/lexical-model-layer": "^15.0.76", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 5bfe577747..d8f57f3a39 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 941a50c72b..6b58c05486 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.75", + "version": "15.0.76", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.75", + "@keymanapp/resources-gosh": "^15.0.76", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76", "@types/node": "^11.9.4" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index c16d867007..99bea45a77 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 81270ce523..0ff3991fc8 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.75", + "version": "15.0.76", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.75", - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75" + "@keymanapp/keyboard-processor": "^15.0.76", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76" }, "devDependencies": { "typescript": "^3.8.3" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index b8d1a7b528..6623bbb5b5 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index 68ffeb55e8..e55821822f 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.75", + "version": "15.0.76", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/web-environment": "^15.0.76", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index 4ade265155..169ce71883 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index 8b8c9673fd..4a7efdcff9 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.75", + "version": "15.0.76", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", + "@keymanapp/resources-gosh": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 7e46153c02..3b4c9806dd 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index e1618a4f72..48386cfb24 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.75", + "version": "15.0.76", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.75" + "@keymanapp/models-wordbreakers": "^15.0.76" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index b1b7079bac..4483454974 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index 1160ebf03b..1113e17edb 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.75", + "version": "15.0.76", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index a42e626aa7..a5387740ec 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index a6143c7546..f49f199735 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.75", + "version": "15.0.76", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.75", + "@keymanapp/models-types": "^15.0.76", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index ec67e11eec..32e888cd7e 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index 857205f836..e89f3e97ed 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.75", + "version": "15.0.76", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/resources-gosh": "^15.0.75", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/resources-gosh": "^15.0.76", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.2", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.75", - "@keymanapp/models-wordbreakers": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75", + "@keymanapp/models-templates": "^15.0.76", + "@keymanapp/models-wordbreakers": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index 4241733660..a58cb7a200 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index 006169292d..4e32d83914 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.75", + "version": "15.0.76", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.75", + "@keymanapp/models-types": "^15.0.76", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.75", - "@keymanapp/models-wordbreakers": "^15.0.75", + "@keymanapp/models-templates": "^15.0.76", + "@keymanapp/models-wordbreakers": "^15.0.76", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/lerna.json b/lerna.json index bc5c9735de..dd7f0bcb36 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.75" + "version": "15.0.76" } diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index 43fa05c510..be7ffb0c4e 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index 152c04841b..71f11fd327 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.75", + "version": "15.0.76", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 7fcf7c70af..97c187e030 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index e43f051033..fdcd382026 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.75", + "version": "15.0.76", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.75" + "@keymanapp/resources-gosh": "^15.0.76" } } diff --git a/web/package-lock.json b/web/package-lock.json index b9ac17af83..ab81fdb766 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.75", + "version": "15.0.76", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index c38071f598..28c496792f 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.75", + "version": "15.0.76", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.75", - "@keymanapp/web-sentry-manager": "^15.0.75", + "@keymanapp/resources-gosh": "^15.0.76", + "@keymanapp/web-sentry-manager": "^15.0.76", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.75", - "@keymanapp/keyboard-processor": "^15.0.75", - "@keymanapp/lexical-model-layer": "^15.0.75", - "@keymanapp/models-types": "^15.0.75", - "@keymanapp/recorder-core": "^15.0.75", - "@keymanapp/web-environment": "^15.0.75", - "@keymanapp/web-utils": "^15.0.75", + "@keymanapp/input-processor": "^15.0.76", + "@keymanapp/keyboard-processor": "^15.0.76", + "@keymanapp/lexical-model-layer": "^15.0.76", + "@keymanapp/models-types": "^15.0.76", + "@keymanapp/recorder-core": "^15.0.76", + "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-utils": "^15.0.76", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" From 600b564bddf028590ea5c7d803bac2069d3ce9d6 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Sun, 27 Jun 2021 12:52:59 +0700 Subject: [PATCH 61/81] chore(android): Add help on Settings option to change spacebar caption --- android/help/about/whatsnew.md | 1 + .../android_images/settings-screen-ap.png | Bin 38603 -> 50467 bytes .../android_images/spacebar-caption-ap.png | Bin 0 -> 12688 bytes android/help/basic/config/index.md | 19 +++++++++++++----- 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 android/help/android_images/spacebar-caption-ap.png diff --git a/android/help/about/whatsnew.md b/android/help/about/whatsnew.md index d89659492b..d585fb3552 100644 --- a/android/help/about/whatsnew.md +++ b/android/help/about/whatsnew.md @@ -4,6 +4,7 @@ title: What's New Here are some of the new features we have added to Keyman 15.0 for Android: * Meet Google Play's target API level requirement of 30 (Android 11) +* Add a settings option to change the displayed keyboard name on the spacebar [![](../android_images/video.png) Watch a video](https://youtu.be/ehk0Bts28JE) that highlights some of these new features. diff --git a/android/help/android_images/settings-screen-ap.png b/android/help/android_images/settings-screen-ap.png index 62554043358766b27fbf84bb1fbecabe7bfac75e..cb88655c7098b2120b878177452ff0489c54b8e3 100644 GIT binary patch literal 50467 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 Q4)90ewaTj!86*Gy2T-+>7ytkO literal 38603 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 B7xVxC diff --git a/android/help/android_images/spacebar-caption-ap.png b/android/help/android_images/spacebar-caption-ap.png new file mode 100644 index 0000000000000000000000000000000000000000..47ae3ab385eccd8c3cd76aac03694cf7d7a43e38 GIT binary patch literal 12688 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 Hc^dU!e9PBH literal 0 HcmV?d00001 diff --git a/android/help/basic/config/index.md b/android/help/basic/config/index.md index 1e8dd29db8..8d797d662b 100644 --- a/android/help/basic/config/index.md +++ b/android/help/basic/config/index.md @@ -38,6 +38,20 @@ For installed keyboard packages, click on this to associate another language wit If all the languages for a keyboard have already been installed, this won't change anything. +## Change Display Language +![](../../android_images/ic_translate_a.png) + +Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale. + +## Spacebar Caption +Click on this to change the displayed keyboard name on the spacebar. The options are: +* Language name +* Keyboard name +* Language name + Keyboard name +* Blank + +![](../../android_images/spacebar-caption-ap.png) + ## Enable Keyman as system-wide keyboard By default, installed Keyman keyboards are only available to use within the Keyman app. Click on this setting to bring up the Android system configuration so Keyman can be added as a system-wide keyboard. @@ -45,11 +59,6 @@ bring up the Android system configuration so Keyman can be added as a system-wid ## Set Keyman as default keyboard Click on this to bring up the Android system configuration for setting Keyman as the default system-wide keyboard. -## Change Display Language -![](../../android_images/ic_translate_a.png) - -Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale. - ## Always show banner This toggle is reserved for future features. When off, the text suggestion banner is only displayed when the dictionary is enabled. The language associated with the keyboard must match the language associated with From 85940888fb33da9de2a833074b62ee667c410dcc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 28 Jun 2021 04:56:52 +1000 Subject: [PATCH 62/81] feat(android): hide textarea for perf Relates to #5258. Unnecessary layout recalc eliminated by hiding the backing textarea with `display:none`. This improved performance by roughly 20%. --- android/KMEA/app/src/main/assets/keyboard.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 3d6b75aec5..83d27639f3 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -41,7 +41,7 @@ ta.readOnly = false; // Tell KMW the default banner height to use - com.keyman.osk.Banner.DEFAULT_HEIGHT = + com.keyman.osk.Banner.DEFAULT_HEIGHT = Math.ceil(window.jsInterface.getDefaultBannerHeight() / window.devicePixelRatio); kmw.addEventListener('keyboardloaded', setIsChiral); @@ -320,7 +320,7 @@ function executePopupKey(keyID, keyText) { var kmw=window['keyman']; - + // KMW only needs keyID to process the popup key. keyText merely logged to console //window.console.log('executePopupKey('+keyID+'); keyText: ' + keyText); kmw['executePopupKey'](keyID); @@ -366,8 +366,6 @@ -
- -
+ From e84a32546f53c775e6f1b55860a98297c252849c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 28 Jun 2021 06:35:02 +1000 Subject: [PATCH 63/81] chore: keyboard_info.source.json 1.0.6 --- .../keyboard_info/keyboard_info.source.json | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json b/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json index 96d2207bb5..ac2687701f 100644 --- a/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json +++ b/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json @@ -1,7 +1,7 @@ -{ +{ "$schema": "http://json-schema.org/schema#", "$ref": "#/definitions/KeyboardInfo", - + "definitions": { "KeyboardInfo": { "type": "object", @@ -28,23 +28,23 @@ "encodings": { "type": "array", "items": { "type": "string", "enum": ["ansi", "unicode"] }, "additionalItems": false }, "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["welcome", "documentation", "fonts", "visualKeyboard"] }, "additionalItems": false }, "version": { "type": "string" }, - "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.0$" }, + "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.\\d$" }, "helpLink": { "type": "string", "pattern": "^https://help\\.keyman\\.com/keyboard/" }, "platformSupport": { "$ref": "#/definitions/KeyboardPlatformInfo" }, "legacyId": { "type": "number" }, "sourcePath": { "type": "string", "pattern": "^(release|legacy|experimental)/.+/.+$" }, - "related": { "type": "object", "patternProperties": { + "related": { "type": "object", "patternProperties": { ".": { "$ref": "#/definitions/KeyboardRelatedInfo" } }, "additionalProperties": false - } + }, + "deprecated": { "type": "boolean" } }, "required": [ "license", "languages" - ], - "additionalProperties": false + ] }, - + "KeyboardLanguageInfo": { "type": "object", "patternProperties": { @@ -52,18 +52,22 @@ }, "additionalProperties": false }, - + "KeyboardLanguageInfoItem": { "type": "object", "properties": { "font": { "$ref": "#/definitions/KeyboardFontInfo" }, "oskFont": { "$ref": "#/definitions/KeyboardFontInfo" }, - "example": { "$ref": "#/definitions/KeyboardExampleInfo" } + "example": { "$ref": "#/definitions/KeyboardExampleInfo" }, + "displayName": { "type": "string" }, + "languageName": { "type": "string" }, + "scriptName": { "type": "string" }, + "regionName": { "type": "string" } }, "required": [], "additionalProperties": false }, - + "KeyboardFontInfo": { "type": "object", "properties": { @@ -81,9 +85,9 @@ "KeyboardExampleInfo": { "type": "object", "properties": { - "keys": { "anyOf": [ + "keys": { "anyOf": [ { "type": "string" }, - { "type": "array", "items": { + { "type": "array", "items": { "anyOf": [ { "type": "string" }, { "$ref": "#/definitions/KeyboardExampleKeyInfo" } @@ -96,7 +100,7 @@ "required": [], "additionalProperties": false }, - + "KeyboardExampleKeyInfo": { "type": "object", "properties": { @@ -106,7 +110,7 @@ "K_N", "K_O", "K_P", "K_Q", "K_R", "K_S", "K_T", "K_U", "K_V", "K_W", "K_X", "K_Y", "K_Z", "K_1", "K_2", "K_3", "K_4", "K_5", "K_6", "K_7", "K_8", "K_9", "K_0", "K_BKQUOTE", "K_HYPHEN", "K_EQUAL", "K_LBRKT", "K_RBRKT", "K_BKSLASH", "K_COLON", - "K_QUOTE", "K_COMMA", "K_PERIOD", "K_SLASH", + "K_QUOTE", "K_COMMA", "K_PERIOD", "K_SLASH", "K_oE2", "K_BKSP", "K_TAB", "K_ENTER", "K_ESC", "K_LEFT", "K_UP", "K_RIGHT", "K_DOWN", "K_PGUP", "K_PGDN", "K_HOME", "K_END", "K_INS", "K_DEL", "K_F1", "K_F2", "K_F3", "K_F4", "K_F5", "K_F6", "K_F7", "K_F8", "K_F9", "K_F10", "K_F11", "K_F12", @@ -135,7 +139,7 @@ "required": ["key"], "additionalProperties": false }, - + "KeyboardLinkInfo": { "type": "object", "properties": { @@ -145,7 +149,7 @@ "required": ["name", "url"], "additionalProperties": false }, - + "KeyboardPlatformInfo": { "type": "object", "patternProperties": { @@ -154,7 +158,7 @@ "required": [], "additionalProperties": false }, - + "KeyboardRelatedInfo": { "type": "object", "properties": { From c72f5990dc90f40740091769e916b963e11a2534 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 28 Jun 2021 06:47:35 +1000 Subject: [PATCH 64/81] chore: update README --- windows/src/global/inst/data/keyboard_info/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/windows/src/global/inst/data/keyboard_info/README.md b/windows/src/global/inst/data/keyboard_info/README.md index fd5149ef16..7b45ebe72b 100644 --- a/windows/src/global/inst/data/keyboard_info/README.md +++ b/windows/src/global/inst/data/keyboard_info/README.md @@ -9,6 +9,12 @@ New versions should be deployed to **keymanapp/keyman/windows/src/global/inst/ke # .keyboard_info version history +## 2019-09-06 1.0.6 stable +* No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). + +## 2018-12-18 1.0.5 stable +* Deprecate languages[] array. Update KeyboardLanguageInfo object to include subtag names. + ## 2018-11-26 1.0.4 stable * Add helpLink field - a link to a keyboard's help page on help.keyman.com if it exists. From 76d21d43441cf644b296acbf1f06d1954af53da0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 28 Jun 2021 15:40:51 +1000 Subject: [PATCH 65/81] chore: address review comments --- .../KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java index 44bca00901..46a1bb9af6 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java @@ -1030,7 +1030,7 @@ final class KMKeyboard extends WebView { * 1. Replace "source" keys for "files" keys * 2. Create full font paths for .ttf or .svg * @param font String font JSON object as a string - * @return String of modified font information with full paths. If font is invalid, return "''" + * @return JSONObject of modified font information with full paths. If font is invalid, return `null` */ private JSONObject makeFontPaths(String font) { From ec9e6aaef8acb98d70e48217a42e2139220e6372 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 28 Jun 2021 14:29:39 +0700 Subject: [PATCH 66/81] Update web/source/kmwbase.ts Co-authored-by: Joshua Horton --- web/source/kmwbase.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index 187f7d86e4..64669a4498 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -322,7 +322,7 @@ namespace com.keyman { * Add default keyboards for given language(s) * * @param {string|string[]} arg Language name (multiple arguments allowed) - * @returns {Promise<(KeyboardStub|ErorStub)[]>} Promise of added keyboard/error stubs + * @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard/error stubs **/ ['addKeyboardsForLanguage'](arg: string[]|string) : Promise<(com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[]> { if (typeof arg === 'string') { @@ -728,4 +728,4 @@ if(!window['keyman'] || !window['keyman']['loaded']) { // TODO: Eliminate the need for this. Will require more refactoring & redesign to drop. window['keyman'].core.languageProcessor.init(); })(); -} \ No newline at end of file +} From 9c8c7c9445d543626e4a612e186e6edfe79f72b1 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 28 Jun 2021 21:50:28 +1000 Subject: [PATCH 67/81] fix(windows): update comments for appint.h --- windows/src/engine/keyman32/appint/appint.cpp | 38 ++++--- windows/src/engine/keyman32/appint/appint.h | 102 +++++++++++++++--- .../tests/keyman32-tests/appinttests.cpp | 41 +++++++ .../keyman32-tests/keyman32-tests.vcxproj | 2 +- .../keyman32/tests/keyman32-tests/test.cpp | 59 ---------- 5 files changed, 149 insertions(+), 93 deletions(-) create mode 100644 windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp delete mode 100644 windows/src/engine/keyman32/tests/keyman32-tests/test.cpp diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index ea6e877fe5..b198b210b8 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -68,7 +68,7 @@ WCHAR *AppContext::Buf(int n) return p; } -WCHAR *AppContext::BufMax(int n) // Used only by IMX DLLs +WCHAR *AppContext::BufMax(int n) { WCHAR *p = wcschr(CurContext, 0); // I3091 @@ -107,34 +107,40 @@ void AppContext::Reset() void AppContext::Get(WCHAR *buf, int bufsize) { - for(WCHAR *p = CurContext; *p && bufsize > 0; p++, bufsize--) - { - *buf = *p; buf++; - if(*p >= 0xD800 && *p <= 0xDBFF) { *buf = *(++p); bufsize--; buf++; } - } - //wcsncpy(buf, CurContext, bufsize); - *buf = 0; - //buf[bufsize-1] = 0; -} - -void AppContext::GetSize(WCHAR* buf, int bufsize) -{ - // surrogate pairs need to be treated as a single unit. + // surrogate pairs need to be treated as a single unit, therefore use + // BuffMax to find the startIndex. // Buff Max handles the case where the surrogate pairs is split // by the buff size WCHAR* startIndex = this->BufMax(bufsize); for (WCHAR* p = startIndex; *p && bufsize > 0; p++, bufsize--) { - *buf = *p; + *buf = *p; if ((*p >= 0xD800 && *p <= 0xDBFF) && (bufsize - 2 > 0)) { buf++; *buf = *(++p); bufsize--; } buf++; } *buf = 0; - } +//void AppContext::GetSize(WCHAR* buf, int bufsize) +//{ + // surrogate pairs need to be treated as a single unit. + // Buff Max handles the case where the surrogate pairs is split + // by the buff size +// WCHAR* startIndex = this->BufMax(bufsize); + +// for (WCHAR* p = startIndex; *p && bufsize > 0; p++, bufsize--) + // { +// *buf = *p; +// if ((*p >= 0xD800 && *p <= 0xDBFF) && (bufsize - 2 > 0)) { buf++; *buf = *(++p); bufsize--; } +// buf++; +// } + +// *buf = 0; + +//} + void AppContext::CopyFrom(AppContext *source) // I3575 { SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 1)); diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index c63ccc1d83..3b831f7b4e 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -1,18 +1,18 @@ /* Name: appint Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 14 Sep 2006 Modified Date: 23 Feb 2016 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 14 Sep 2006 - mcdurdin - Remove NoSetShift flag 27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version @@ -74,7 +74,7 @@ public: AppActionQueue(); virtual void ResetQueue(); virtual BOOL QueueAction(int ItemType, DWORD dwData); - + BOOL IsQueueEmpty() { return QueueSize == 0; } int GetQueueSize() { return QueueSize; } }; @@ -82,21 +82,89 @@ public: class AppContext { private: - WCHAR CurContext[MAXCONTEXT]; + WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and is null terminated. int pos; public: AppContext(); + /** + * @brief Copy "source" AppContext to this AppContext + * + * @param source AppContext to copy + */ void CopyFrom(AppContext *source); - void Add(WCHAR ch); + + /** + * @brief Add a single code unit to the Current Context. Not necessarily a complete code point + * + * @param Code unit to add + */ + void Add(WCHAR ch); + + /** + * @brief Removes a single code point from the end of the CurContext closest to the caret + * - ie it will be both code units if a surrogate pair. If it is a deadkey it will remove + * the deadkey code point and the sentinal code point. + */ void Delete(); - void Reset(); + + /** + * @brief Clears the CurContext and resets the position - pos - index + * + */ + void Reset(); + + /** + * @brief Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than buffsize if that would + * have meant spliting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize the number of code units ie size of the WCHAR buffer - not the code points + */ void Get(WCHAR *buf, int bufsize); - void GetSize(WCHAR* buf, int bufsize); + + /** + * @brief Sets the CurContext to the supplied buf character array and updates the pos index. + * + * @param buf + */ void Set(const WCHAR *buf); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n valid characters remaining until the the null terminating character. + * eg it will be one code unit less than buffsize if that would + * have meant spliting a surrogate pair + * + * @param n The maximum number of valid characters - (code points) not WCHAR size (code units) + * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + */ WCHAR *BufMax(int n); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n characters remmaining until the the null terminating character. + * Note: Unlike BufMax there is no checking for truncation of code points. + * e.g. the pointer may point to a code unit that its half of a surrogate pair + * + * @param n + * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + */ WCHAR *Buf(int n); + + /** + * @brief Returns TRUE if the latest code unit in the context is a deadkey + * + * @return BOOL + */ BOOL CharIsDeadkey(); + + /** + * @brief Returns TRUE if the latest code unit in the CurContext is a surrogate pair. + * @return BOOL + */ BOOL CharIsSurrogatePair(); }; @@ -120,28 +188,28 @@ public: //BOOL NoSetShift; /* Information functions */ - + virtual BOOL CanHandleWindow(HWND ahwnd) = 0; virtual BOOL IsWindowHandled(HWND ahwnd) = 0; virtual BOOL HandleWindow(HWND ahwnd) = 0; virtual BOOL IsUnicode() = 0; /* Context functions */ - + virtual void ReadContext() = 0; virtual void ResetContext() = 0; virtual void AddContext(WCHAR ch) = 0; //I2436 virtual WCHAR *ContextBuf(int n) = 0; virtual WCHAR *ContextBufMax(int n) = 0; - + /* Queue and sending functions */ - + virtual BOOL QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags) = 0; void SetCurrentShiftState(int ShiftFlags) { FShiftFlags = ShiftFlags; } virtual BOOL SendActions() = 0; // I4196 }; extern const LPSTR ItemTypes[]; - + #endif diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp b/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp new file mode 100644 index 0000000000..4b784ca14f --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp @@ -0,0 +1,41 @@ +#include "pch.h" + + +// Test calling buffer of MAXCONTEXT size +TEST(AppContext, Get_Max_buff) { + WCHAR callbuf[MAXCONTEXT]; + AppContext testContext; + WCHAR *testString = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789"; + WCHAR *expectedString = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0"; // MAXCONTEXT taking into account null character termination + testContext.Set(testString); + testContext.Get(callbuf, MAXCONTEXT); + EXPECT_STREQ((expectedString), callbuf); +} +// Test for a calling buffer smaller than AppContext's current context size. +// Expected result should return subset with charcters closet +// to the caret. +TEST(AppContext, Get_small_buff) { + WCHAR callbuf[MAXCONTEXT]; + AppContext testContext; + testContext.Set(L"abcdefghijk"); + testContext.Get(callbuf, 5); + EXPECT_STREQ((L"ghijk"), callbuf); +} + +// Test with the calling buffer size that would only have enough bytes for one +// surrogate of a surrogate pair. +TEST(AppContext, Get_split_surrogate) { + + WCHAR callbuff[MAXCONTEXT]; + AppContext testContext; + WCHAR testString[] = { 0x0042, 0x0042, 0xD800, 0xDC00, 0x0043, 0x0044, 0x0000 }; + WCHAR expectedString[] = { 0x0043, 0x0044, 0x0000 }; + + testContext.Set(testString); + // split the surrogate should return on 16-bit code unit less than requested + testContext.Get(callbuff, 3); + EXPECT_STREQ((expectedString), callbuff); + // This will should be the same size as the split surrogate + testContext.Get(callbuff, 2); + EXPECT_STREQ((expectedString), callbuff); +} diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj index 171ae81cd8..abcd41e766 100644 --- a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj @@ -54,7 +54,7 @@ - + Create Create diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp b/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp deleted file mode 100644 index 27fbc17585..0000000000 --- a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "pch.h" - - -TEST(TestCaseName, TestName) { - EXPECT_EQ(1, 1); - EXPECT_TRUE(true); -} - -//int xstrlen(PWSTR p); - -TEST(xstring, xstrlen) { - EXPECT_EQ(3, xstrlen(L"abc")); -} - -// Test Context should return bcd -TEST(AppContext, Get_small_buff) { - - WCHAR tmpbuf[MAXCONTEXT]; - AppContext myContext; - myContext.Set(L"abcd"); - myContext.Get(tmpbuf, 3); -// EXPECT_STREQ((L"bcd"), tmpbuf); -} - -// Test Context should return bcd -TEST(AppContext, GetSize_small_buff) { - - WCHAR tmpbuf[MAXCONTEXT]; - AppContext myContext; - myContext.Set(L"abcd"); - myContext.GetSize(tmpbuf, 3); - EXPECT_STREQ((L"bcd"), tmpbuf); -} - -// Test with the surrage pair in the middle of the size buffer requested -// Set the context with a string containing a surratage pair. - -TEST(AppContext, Get_over_run_buf) { - - WCHAR tmpbuf[MAXCONTEXT]; - AppContext myContext; - - // myContext.Set(L"a𐐷bc4"); - //myContext.GetSize(tmpbuf, 10); - - WCHAR testString[] = { 0x0042, 0x0042, 0xD800, 0xDC00, 0x0043, 0x0044, 0x0000 }; - WCHAR expectedString[] = { 0x0043, 0x0044, 0x0000 }; - // Test for breaking at surrogate and one less should give the same string. - myContext.Set(testString); - myContext.GetSize(tmpbuf, 10); - - EXPECT_STREQ((testString), tmpbuf); - // split the surrogate should return on less than requested - myContext.GetSize(tmpbuf, 3); - EXPECT_STREQ((expectedString), tmpbuf); - // This will should be the same size as the split surrogate - myContext.GetSize(tmpbuf, 2); - EXPECT_STREQ((expectedString), tmpbuf); -} From d2b6a00ef210827c4c06bf5e60aad19e8f44bb72 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 28 Jun 2021 14:02:56 -0400 Subject: [PATCH 68/81] auto: increment master version to 15.0.77 --- HISTORY.md | 13 ++++++++++++ VERSION.md | 2 +- .../web/input-processor/package-lock.json | 2 +- common/core/web/input-processor/package.json | 16 +++++++-------- .../web/keyboard-processor/package-lock.json | 2 +- .../core/web/keyboard-processor/package.json | 10 +++++----- .../core/web/tools/recorder/package-lock.json | 2 +- common/core/web/tools/recorder/package.json | 10 +++++----- .../tools/sentry-manager/package-lock.json | 2 +- .../web/tools/sentry-manager/package.json | 4 ++-- common/core/web/utils/package-lock.json | 2 +- common/core/web/utils/package.json | 6 +++--- common/models/templates/package-lock.json | 2 +- common/models/templates/package.json | 8 ++++---- common/models/types/package-lock.json | 2 +- common/models/types/package.json | 2 +- common/models/wordbreakers/package-lock.json | 2 +- common/models/wordbreakers/package.json | 4 ++-- common/predictive-text/package-lock.json | 2 +- common/predictive-text/package.json | 14 ++++++------- developer/js/package-lock.json | 2 +- developer/js/package.json | 8 ++++---- lerna.json | 2 +- resources/gosh/package-lock.json | 2 +- resources/gosh/package.json | 2 +- resources/web-environment/package-lock.json | 2 +- resources/web-environment/package.json | 4 ++-- web/package-lock.json | 2 +- web/package.json | 20 +++++++++---------- 29 files changed, 82 insertions(+), 69 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index e4add6eb70..4fb6081f34 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,18 @@ # Keyman Version History +## 15.0.76 alpha 2021-06-28 + +* feat(web): Add option useAlerts to control alerts() (#5302) +* chore(android): Add help on Settings option to change spacebar caption (#5375) +* feat(android): hide textarea for perf (#5376) +* chore: keyboard_info.source.json 1.0.6 (#5379) +* refactor(android): setKeyboard and setKeymanLanguage (#5338) +* feat(web): Merge keyboard stub with error stub (#5340) +* feat(linux): Add reference index page (#5370) +* fix(linux): Fix restarting ibus when running with sudo (#5371) +* refactor(linux): Centralize paths (#5372) +* fix(linux): Save QR code in temporary directory (#5373) + ## 15.0.75 alpha 2021-06-25 * fix(android): rotation is not updating keyboard (#5247) diff --git a/VERSION.md b/VERSION.md index 9052ffd952..3eb29cef60 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.76 \ No newline at end of file +15.0.77 \ No newline at end of file diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 7217b2c075..7bf0ddcaef 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index 89dd3d1c55..f49a42794e 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.76", + "version": "15.0.77", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.76", - "@keymanapp/resources-gosh": "^15.0.76", + "@keymanapp/lexical-model-compiler": "^15.0.77", + "@keymanapp/resources-gosh": "^15.0.77", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.76", - "@keymanapp/lexical-model-layer": "^15.0.76", - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76", + "@keymanapp/keyboard-processor": "^15.0.77", + "@keymanapp/lexical-model-layer": "^15.0.77", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index d8f57f3a39..75623694ea 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 6b58c05486..9934acc4a6 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.76", + "version": "15.0.77", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.76", + "@keymanapp/resources-gosh": "^15.0.77", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77", "@types/node": "^11.9.4" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index 99bea45a77..ceb817425f 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 0ff3991fc8..95d78a13b5 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.76", + "version": "15.0.77", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.76", - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76" + "@keymanapp/keyboard-processor": "^15.0.77", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77" }, "devDependencies": { "typescript": "^3.8.3" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 6623bbb5b5..223c8e1531 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index e55821822f..7a46c42554 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.76", + "version": "15.0.77", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/web-environment": "^15.0.77", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index 169ce71883..20fe4e1542 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index 4a7efdcff9..586d102eda 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.76", + "version": "15.0.77", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", + "@keymanapp/resources-gosh": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 3b4c9806dd..67df69d606 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index 48386cfb24..1dc32b48bb 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.76", + "version": "15.0.77", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.76" + "@keymanapp/models-wordbreakers": "^15.0.77" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index 4483454974..9d7e6229fb 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index 1113e17edb..fac36500c9 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.76", + "version": "15.0.77", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index a5387740ec..b5a1a89242 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index f49f199735..13b7277b26 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.76", + "version": "15.0.77", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.76", + "@keymanapp/models-types": "^15.0.77", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 32e888cd7e..4064c6f91c 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index e89f3e97ed..d9bf51b1fe 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.76", + "version": "15.0.77", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/resources-gosh": "^15.0.76", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/resources-gosh": "^15.0.77", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.2", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.76", - "@keymanapp/models-wordbreakers": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76", + "@keymanapp/models-templates": "^15.0.77", + "@keymanapp/models-wordbreakers": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index a58cb7a200..61cc0542d5 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index 4e32d83914..7f96d7fbf9 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.76", + "version": "15.0.77", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.76", + "@keymanapp/models-types": "^15.0.77", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.76", - "@keymanapp/models-wordbreakers": "^15.0.76", + "@keymanapp/models-templates": "^15.0.77", + "@keymanapp/models-wordbreakers": "^15.0.77", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/lerna.json b/lerna.json index dd7f0bcb36..0b68042b6d 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.76" + "version": "15.0.77" } diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index be7ffb0c4e..003b2b8d8d 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index 71f11fd327..611be7b50c 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.76", + "version": "15.0.77", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 97c187e030..245ba2d32e 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index fdcd382026..f8e30b6a15 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.76", + "version": "15.0.77", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.76" + "@keymanapp/resources-gosh": "^15.0.77" } } diff --git a/web/package-lock.json b/web/package-lock.json index ab81fdb766..7fa09a2001 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.76", + "version": "15.0.77", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index 28c496792f..7f452e052a 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.76", + "version": "15.0.77", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.76", - "@keymanapp/web-sentry-manager": "^15.0.76", + "@keymanapp/resources-gosh": "^15.0.77", + "@keymanapp/web-sentry-manager": "^15.0.77", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.76", - "@keymanapp/keyboard-processor": "^15.0.76", - "@keymanapp/lexical-model-layer": "^15.0.76", - "@keymanapp/models-types": "^15.0.76", - "@keymanapp/recorder-core": "^15.0.76", - "@keymanapp/web-environment": "^15.0.76", - "@keymanapp/web-utils": "^15.0.76", + "@keymanapp/input-processor": "^15.0.77", + "@keymanapp/keyboard-processor": "^15.0.77", + "@keymanapp/lexical-model-layer": "^15.0.77", + "@keymanapp/models-types": "^15.0.77", + "@keymanapp/recorder-core": "^15.0.77", + "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-utils": "^15.0.77", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" From 3e627a2f1e95cc51299ea11125e71031dbf85652 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 29 Jun 2021 10:38:19 +1000 Subject: [PATCH 69/81] fix(windows): apply suggestions from code review Co-authored-by: Marc Durdin --- windows/src/engine/keyman32/appint/appint.cpp | 16 ++++--- windows/src/engine/keyman32/appint/appint.h | 47 +++++++++---------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index b198b210b8..7f0df7a9a9 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -108,15 +108,17 @@ void AppContext::Reset() void AppContext::Get(WCHAR *buf, int bufsize) { // surrogate pairs need to be treated as a single unit, therefore use - // BuffMax to find the startIndex. - // Buff Max handles the case where the surrogate pairs is split - // by the buff size - WCHAR* startIndex = this->BufMax(bufsize); - - for (WCHAR* p = startIndex; *p && bufsize > 0; p++, bufsize--) + // BufMax to find a start index. + // BufMax handles the case where a surrogate pair at the + // start of the buffer is split by bufsize + for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) { *buf = *p; - if ((*p >= 0xD800 && *p <= 0xDBFF) && (bufsize - 2 > 0)) { buf++; *buf = *(++p); bufsize--; } + if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) { + buf++; p++; + *buf = *p; + bufsize--; + } buf++; } diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 3b831f7b4e..064a5650cd 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -82,7 +82,7 @@ public: class AppContext { private: - WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and is null terminated. + WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated. int pos; public: @@ -102,9 +102,9 @@ public: void Add(WCHAR ch); /** - * @brief Removes a single code point from the end of the CurContext closest to the caret - * - ie it will be both code units if a surrogate pair. If it is a deadkey it will remove - * the deadkey code point and the sentinal code point. + * @brief Removes a single code point from the end of the CurContext closest to the caret; + * i.e. it will be both code units if a surrogate pair. If it is a deadkey it will + * remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value. */ void Delete(); @@ -115,13 +115,13 @@ public: void Reset(); /** - * @brief Copies the characters in CurContext to supplied buffer. - * If bufsize is reached before the entire context was copied, the buf - * will be truncated to number of valid characters possible with null character - * termination. e.g. it will be one code unit less than buffsize if that would - * have meant spliting a surrogate pair - * @param buf The data buffer to copy current context - * @param bufsize the number of code units ie size of the WCHAR buffer - not the code points + * @brief Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize The number of code units ie size of the WCHAR buffer - not the code points */ void Get(WCHAR *buf, int bufsize); @@ -133,21 +133,21 @@ public: void Set(const WCHAR *buf); /** - * @brief Returns a pointer to the character in the current context buffer which - * will have at most n valid characters remaining until the the null terminating character. - * eg it will be one code unit less than buffsize if that would - * have meant spliting a surrogate pair + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n valid xstring units remaining until the null terminating + * character. It will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair or deadkey. * - * @param n The maximum number of valid characters - (code points) not WCHAR size (code units) - * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + * @param n The maximum number of valid xstring units (not code points or code units) + * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units */ WCHAR *BufMax(int n); /** - * @brief Returns a pointer to the character in the current context buffer which - * will have at most n characters remmaining until the the null terminating character. - * Note: Unlike BufMax there is no checking for truncation of code points. - * e.g. the pointer may point to a code unit that its half of a surrogate pair + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n code units remaining until the the null terminating character. + * Note: Unlike BufMax there is no checking for truncation of code points. + * e.g. the pointer may point to a code unit that is half of a surrogate pair. * * @param n * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters @@ -155,14 +155,14 @@ public: WCHAR *Buf(int n); /** - * @brief Returns TRUE if the latest code unit in the context is a deadkey + * @brief Returns TRUE if the last xstring unit in the context is a deadkey * * @return BOOL */ BOOL CharIsDeadkey(); /** - * @brief Returns TRUE if the latest code unit in the CurContext is a surrogate pair. + * @brief Returns TRUE if the last xstring unit in the CurContext is a surrogate pair. * @return BOOL */ BOOL CharIsSurrogatePair(); @@ -212,4 +212,3 @@ public: extern const LPSTR ItemTypes[]; #endif - From 30ac134b8a67f288ede6a3fde6164d2d962a9f83 Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 29 Jun 2021 10:50:55 +1000 Subject: [PATCH 70/81] fix(windows): remove dead code in appcontext --- windows/src/engine/keyman32/appint/appint.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index 7f0df7a9a9..1ca6eff025 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -125,24 +125,6 @@ void AppContext::Get(WCHAR *buf, int bufsize) *buf = 0; } -//void AppContext::GetSize(WCHAR* buf, int bufsize) -//{ - // surrogate pairs need to be treated as a single unit. - // Buff Max handles the case where the surrogate pairs is split - // by the buff size -// WCHAR* startIndex = this->BufMax(bufsize); - -// for (WCHAR* p = startIndex; *p && bufsize > 0; p++, bufsize--) - // { -// *buf = *p; -// if ((*p >= 0xD800 && *p <= 0xDBFF) && (bufsize - 2 > 0)) { buf++; *buf = *(++p); bufsize--; } -// buf++; -// } - -// *buf = 0; - -//} - void AppContext::CopyFrom(AppContext *source) // I3575 { SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 1)); From 87d35c6d1c0a983c0454fdaceeed708018e941e9 Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 29 Jun 2021 16:42:53 +1000 Subject: [PATCH 71/81] fix(common): get handle caller buffer size small than internal Fixes #5336 --- common/core/desktop/src/kmx/kmx_context.cpp | 24 ++++++- common/core/desktop/src/kmx/kmx_context.h | 76 ++++++++++++++++++++- windows/src/engine/keyman32/appint/appint.h | 1 - 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/common/core/desktop/src/kmx/kmx_context.cpp b/common/core/desktop/src/kmx/kmx_context.cpp index ffea32be4c..3a2f20b8c3 100644 --- a/common/core/desktop/src/kmx/kmx_context.cpp +++ b/common/core/desktop/src/kmx/kmx_context.cpp @@ -29,6 +29,18 @@ void KMX_Context::Add(KMX_WCHAR ch) //DebugLog("KMX_Context::Add(%x): EXIT [%d]: %s", ch, pos, Debug_UnicodeString(CurContext)); } +KMX_WCHAR *KMX_Context::BufMax(int n) +{ + KMX_WCHAR *p = (KMX_WCHAR *) u16chr(CurContext, 0); + if(CurContext == p || n == 0) return p; // empty context or 0 characters requested, return pointer to end of context + + KMX_WCHAR *q = p; + for(; p != NULL && p > CurContext && (intptr_t)(q-p) < n; p = decxstr(p, CurContext)); + + if((intptr_t)(q-p) > n) p = incxstr(p); // Copes with deadkey or supplementary pair at start of returned buffer making it too long + + return p; +} KMX_WCHAR *KMX_Context::Buf(int n) { @@ -63,11 +75,17 @@ void KMX_Context::Reset() void KMX_Context::Get(KMX_WCHAR *buf, int bufsize) { - for(KMX_WCHAR *p = CurContext; *p && bufsize > 0; p++, bufsize--) + for (KMX_WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) { - *buf = *p; buf++; - if(*p >= 0xD800 && *p <= 0xDBFF) { *buf = *(++p); bufsize--; buf++; } + *buf = *p; + if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) { + buf++; p++; + *buf = *p; + bufsize--; + } + buf++; } + *buf = 0; } diff --git a/common/core/desktop/src/kmx/kmx_context.h b/common/core/desktop/src/kmx/kmx_context.h index 8e3664d82f..81a05432d0 100644 --- a/common/core/desktop/src/kmx/kmx_context.h +++ b/common/core/desktop/src/kmx/kmx_context.h @@ -21,15 +21,89 @@ private: public: KMX_Context(); + + /** + * @brief Copy "source" AppContext to this AppContext + * + * @param source KMX_Context to copy + */ void CopyFrom(KMX_Context *source); + + /** + * @brief Add a single code unit to the Current Context. Not necessarily a complete code point + * + * @param Code unit to add + */ void Add(KMX_WCHAR ch); + + /** + * @brief Removes a single code point from the end of the CurContext closest to the caret; + * i.e. it will be both code units if a surrogate pair. If it is a deadkey it will + * remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value. + */ void Delete(); + + /** + * @brief Clears the CurContext and resets the position - pos - index + */ void Reset(); + + /** + * @brief Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize The number of code units ie size of the KMX_WCHAR buffer - not the code points + */ void Get(KMX_WCHAR *buf, int bufsize); + + /** + * @brief Sets the CurContext to the supplied buf character array and updates the pos index. + * + * @param buf + */ void Set(const KMX_WCHAR *buf); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n valid xstring units remaining until the null terminating + * character. It will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair or deadkey. + * + * @param n The maximum number of valid xstring units (not code points or code units) + * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units + */ KMX_WCHAR *Buf(int n); - KMX_WCHAR *GetFullContext() { return CurContext; } + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n code units remaining until the the null terminating character. + * Note: Unlike BufMax there is no checking for truncation of code points. + * e.g. the pointer may point to a code unit that is half of a surrogate pair. + * + * @param n + * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + */ + KMX_WCHAR *BufMax(int n); + + /** + * @brief Return a pointer to the CurContext buffer + * + * @return KMX_WCHAR* + */ + const KMX_WCHAR *GetFullContext() { return CurContext; } + /** + * @brief Returns TRUE if the last xstring unit in the context is a deadkey + * + * @return BOOL + */ KMX_BOOL CharIsDeadkey(); + /** + * @brief Returns TRUE if the last xstring unit in the CurContext is a surrogate pair. + * @return BOOL + */ KMX_BOOL CharIsSurrogatePair(); }; diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 064a5650cd..55f6c4a693 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -110,7 +110,6 @@ public: /** * @brief Clears the CurContext and resets the position - pos - index - * */ void Reset(); From f3b59b7dd915021a6afd7622a033c1f14d6b8805 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 29 Jun 2021 16:49:07 +1000 Subject: [PATCH 72/81] fix(developer): editor commands were ignored As we changed the User Agent string to have 'Keyman' instead of 'TIKE', the editor commands were being ignored. --- windows/src/developer/TIKE/xml/app/editor/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/developer/TIKE/xml/app/editor/editor.js b/windows/src/developer/TIKE/xml/app/editor/editor.js index ce070f0115..be447ac965 100644 --- a/windows/src/developer/TIKE/xml/app/editor/editor.js +++ b/windows/src/developer/TIKE/xml/app/editor/editor.js @@ -389,7 +389,7 @@ async function loadSettings() { var commands = []; var command = function (cmd) { - if (navigator.userAgent.indexOf('TIKE') < 0) { + if (navigator.userAgent.indexOf('Keyman') < 0) { // Don't execute command if not in the correct host application (allows for browser-based testing) return false; } From 5507ceb7ed05013ca85fc6bb14e4e7262eeca94d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 29 Jun 2021 16:51:34 +1000 Subject: [PATCH 73/81] fix(developer): builder commands were ignored As we changed the User Agent string to have 'Keyman' instead of 'TIKE', the builder commands were being ignored. --- windows/src/developer/TIKE/xml/layoutbuilder/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js index 1e9133c2b0..4becea28f7 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js @@ -1119,7 +1119,7 @@ $(function () { this.commands = []; this.command = function (cmd) { - if (navigator.userAgent.indexOf('TIKE') < 0) { + if (navigator.userAgent.indexOf('Keyman') < 0) { return false; } // queue commands to build a single portmanteau command when we return to idle From e83594038f1abceff3e104a4ddcd4b47f04f364c Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 29 Jun 2021 17:03:31 +1000 Subject: [PATCH 74/81] fix(common): errors in comments --- common/core/desktop/src/kmx/kmx_context.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/core/desktop/src/kmx/kmx_context.h b/common/core/desktop/src/kmx/kmx_context.h index 81a05432d0..111d08104c 100644 --- a/common/core/desktop/src/kmx/kmx_context.h +++ b/common/core/desktop/src/kmx/kmx_context.h @@ -73,7 +73,7 @@ public: * have meant splitting a surrogate pair or deadkey. * * @param n The maximum number of valid xstring units (not code points or code units) - * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units + * @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n xstring units */ KMX_WCHAR *Buf(int n); @@ -84,7 +84,7 @@ public: * e.g. the pointer may point to a code unit that is half of a surrogate pair. * * @param n - * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + * @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n characters */ KMX_WCHAR *BufMax(int n); From 7c8d75380938009148c8032a286076efe940177f Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 29 Jun 2021 14:25:55 +0700 Subject: [PATCH 75/81] fix(web): console errors when using SHIFT w help-text 'osk' --- web/source/osk/oskManager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/source/osk/oskManager.ts b/web/source/osk/oskManager.ts index 66fd1ae0de..c9add000a4 100644 --- a/web/source/osk/oskManager.ts +++ b/web/source/osk/oskManager.ts @@ -313,7 +313,11 @@ namespace com.keyman.osk { source: text.MutableSystemStore, newValue: string) { if(source.value != newValue) { - this.vkbd.layerId = newValue; + // Prevents console errors when a keyboard only displays help. + // Can occur when using SHIFT with sil_euro_latin on a desktop form-factor. + if(this.vkbd) { + this.vkbd.layerId = newValue; + } this._Show(); } }.bind(this); From 889718830e350bd3c2afda6743f7047c410e14bb Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 29 Jun 2021 17:29:33 +1000 Subject: [PATCH 76/81] fix(common): make xcode happy about empty loop --- common/core/desktop/src/kmx/kmx_context.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/core/desktop/src/kmx/kmx_context.cpp b/common/core/desktop/src/kmx/kmx_context.cpp index 3a2f20b8c3..6efa11f94f 100644 --- a/common/core/desktop/src/kmx/kmx_context.cpp +++ b/common/core/desktop/src/kmx/kmx_context.cpp @@ -35,7 +35,8 @@ KMX_WCHAR *KMX_Context::BufMax(int n) if(CurContext == p || n == 0) return p; // empty context or 0 characters requested, return pointer to end of context KMX_WCHAR *q = p; - for(; p != NULL && p > CurContext && (intptr_t)(q-p) < n; p = decxstr(p, CurContext)); + for(; p != NULL && p > CurContext && (intptr_t)(q-p) < n; p = decxstr(p, CurContext)) + ; // ; on new line to tell compiler empty loop is intentional if((intptr_t)(q-p) > n) p = incxstr(p); // Copes with deadkey or supplementary pair at start of returned buffer making it too long From 371f031d0dcc4f5207a7f946683981c6de8d90e7 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 29 Jun 2021 14:01:36 -0400 Subject: [PATCH 77/81] auto: increment master version to 15.0.78 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- .../web/input-processor/package-lock.json | 2 +- common/core/web/input-processor/package.json | 16 +++++++-------- .../web/keyboard-processor/package-lock.json | 2 +- .../core/web/keyboard-processor/package.json | 10 +++++----- .../core/web/tools/recorder/package-lock.json | 2 +- common/core/web/tools/recorder/package.json | 10 +++++----- .../tools/sentry-manager/package-lock.json | 2 +- .../web/tools/sentry-manager/package.json | 4 ++-- common/core/web/utils/package-lock.json | 2 +- common/core/web/utils/package.json | 6 +++--- common/models/templates/package-lock.json | 2 +- common/models/templates/package.json | 8 ++++---- common/models/types/package-lock.json | 2 +- common/models/types/package.json | 2 +- common/models/wordbreakers/package-lock.json | 2 +- common/models/wordbreakers/package.json | 4 ++-- common/predictive-text/package-lock.json | 2 +- common/predictive-text/package.json | 14 ++++++------- developer/js/package-lock.json | 2 +- developer/js/package.json | 8 ++++---- lerna.json | 2 +- resources/gosh/package-lock.json | 2 +- resources/gosh/package.json | 2 +- resources/web-environment/package-lock.json | 2 +- resources/web-environment/package.json | 4 ++-- web/package-lock.json | 2 +- web/package.json | 20 +++++++++---------- 29 files changed, 74 insertions(+), 69 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 4fb6081f34..3179b65fe3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 15.0.77 alpha 2021-06-29 + +* fix(windows): #5336 appcontext get to handle small buffer (#5383) +* fix(common): get handle caller buffer size small than internal Fixes … (#5390) + ## 15.0.76 alpha 2021-06-28 * feat(web): Add option useAlerts to control alerts() (#5302) diff --git a/VERSION.md b/VERSION.md index 3eb29cef60..52c9161411 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.77 \ No newline at end of file +15.0.78 \ No newline at end of file diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 7bf0ddcaef..157d3314c2 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index f49a42794e..8b1ee36ba6 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.77", + "version": "15.0.78", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.77", - "@keymanapp/resources-gosh": "^15.0.77", + "@keymanapp/lexical-model-compiler": "^15.0.78", + "@keymanapp/resources-gosh": "^15.0.78", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.77", - "@keymanapp/lexical-model-layer": "^15.0.77", - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77", + "@keymanapp/keyboard-processor": "^15.0.78", + "@keymanapp/lexical-model-layer": "^15.0.78", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 75623694ea..700809350f 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 9934acc4a6..4d2e1d9672 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.77", + "version": "15.0.78", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.77", + "@keymanapp/resources-gosh": "^15.0.78", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78", "@types/node": "^11.9.4" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index ceb817425f..47a01b36d7 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 95d78a13b5..088861d40c 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.77", + "version": "15.0.78", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.77", - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77" + "@keymanapp/keyboard-processor": "^15.0.78", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78" }, "devDependencies": { "typescript": "^3.8.3" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 223c8e1531..dc1a750683 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index 7a46c42554..925bf6792b 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.77", + "version": "15.0.78", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/web-environment": "^15.0.78", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index 20fe4e1542..d2c9edacaa 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index 586d102eda..1bde8f815c 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.77", + "version": "15.0.78", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", + "@keymanapp/resources-gosh": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 67df69d606..2b2dbc89aa 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index 1dc32b48bb..89b8e2987c 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.77", + "version": "15.0.78", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.77" + "@keymanapp/models-wordbreakers": "^15.0.78" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index 9d7e6229fb..cd88c2efd9 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index fac36500c9..a184ea1f40 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.77", + "version": "15.0.78", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index b5a1a89242..d3a4273ba4 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 13b7277b26..4d989ffd3c 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.77", + "version": "15.0.78", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.77", + "@keymanapp/models-types": "^15.0.78", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 4064c6f91c..0cdcf495d4 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index d9bf51b1fe..c0c707471a 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.77", + "version": "15.0.78", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/resources-gosh": "^15.0.77", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/resources-gosh": "^15.0.78", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.2", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.77", - "@keymanapp/models-wordbreakers": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77", + "@keymanapp/models-templates": "^15.0.78", + "@keymanapp/models-wordbreakers": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index 61cc0542d5..a6f3e142a6 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index 7f96d7fbf9..9b971ea4e3 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.77", + "version": "15.0.78", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.77", + "@keymanapp/models-types": "^15.0.78", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.77", - "@keymanapp/models-wordbreakers": "^15.0.77", + "@keymanapp/models-templates": "^15.0.78", + "@keymanapp/models-wordbreakers": "^15.0.78", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/lerna.json b/lerna.json index 0b68042b6d..eb849b2e38 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.77" + "version": "15.0.78" } diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index 003b2b8d8d..0563da842d 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index 611be7b50c..f1cba0a468 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.77", + "version": "15.0.78", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 245ba2d32e..a746a8f6c6 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index f8e30b6a15..f46776e58b 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.77", + "version": "15.0.78", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.77" + "@keymanapp/resources-gosh": "^15.0.78" } } diff --git a/web/package-lock.json b/web/package-lock.json index 7fa09a2001..b8819e53d4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.77", + "version": "15.0.78", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index 7f452e052a..20f1ff2965 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.77", + "version": "15.0.78", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.77", - "@keymanapp/web-sentry-manager": "^15.0.77", + "@keymanapp/resources-gosh": "^15.0.78", + "@keymanapp/web-sentry-manager": "^15.0.78", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.77", - "@keymanapp/keyboard-processor": "^15.0.77", - "@keymanapp/lexical-model-layer": "^15.0.77", - "@keymanapp/models-types": "^15.0.77", - "@keymanapp/recorder-core": "^15.0.77", - "@keymanapp/web-environment": "^15.0.77", - "@keymanapp/web-utils": "^15.0.77", + "@keymanapp/input-processor": "^15.0.78", + "@keymanapp/keyboard-processor": "^15.0.78", + "@keymanapp/lexical-model-layer": "^15.0.78", + "@keymanapp/models-types": "^15.0.78", + "@keymanapp/recorder-core": "^15.0.78", + "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-utils": "^15.0.78", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" From 2034818485d56ea2535ecc640c0fd499a985239e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 30 Jun 2021 13:03:30 +1000 Subject: [PATCH 78/81] chore: address review comments --- docs/build/macos.md | 16 +++++++------- docs/build/old/android-notes.md | 9 +++++--- docs/build/sentry-cli.md | 7 +++--- resources/devbox/macos/macos.sh | 38 +++++++++++++++++++++++++++------ 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/docs/build/macos.md b/docs/build/macos.md index db914ad5aa..41a45f7ad2 100644 --- a/docs/build/macos.md +++ b/docs/build/macos.md @@ -34,22 +34,21 @@ Many dependencies are only required for specific projects. The remaining dependencies can be installed via script: `resources/devbox/macos/macos.sh` - + This script will also update your environment to the values in: `resources/devbox/macos/keyman.macos.env.sh` - + It will also add these environment settings to your `~/.bashrc`. These dependencies are also listed below if you'd prefer to install manually. ## Shared Dependencies -* Shared: HomeBrew, Bash 5.0+, jq, Python 3, Meson, Ninja, Rust, coreutils +* Shared: HomeBrew, Bash 5.0+, jq, Python 3, Meson, Ninja, Rust, coreutils, Pandoc ```shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - brew install bash jq python3 meson ninja rustup-init coreutils - brew install rustup-init + brew install bash jq python3 meson ninja rustup-init coreutils pandoc rustup-init ``` * Web: node.js, emscripten, wasm-pack, openjdk 8 @@ -60,7 +59,7 @@ These dependencies are also listed below if you'd prefer to install manually. ``` * iOS: swiftlint, carthage - + ```shell brew install swiftlint carthage ``` @@ -71,10 +70,10 @@ These dependencies are also listed below if you'd prefer to install manually. brew install carthage cocoapods ``` -* Android: openjdk 8, Android SDK, Android Studio, Ant, Gradle, Maven, Pandoc +* Android: openjdk 8, Android SDK, Android Studio, Ant, Gradle, Maven ```shell - brew install openjdk@8 android-sdk android-studio ant gradle maven pandoc + brew install openjdk@8 android-sdk android-studio ant gradle maven # update path source ../resources/devbox/macos/keyman.macos.env.sh # optionally install sdk images @@ -99,3 +98,4 @@ These dependencies are also listed below if you'd prefer to install manually. ``` * Run Android Studio once after installation to install additional components + such as emulator images and SDK updates. diff --git a/docs/build/old/android-notes.md b/docs/build/old/android-notes.md index 512044b881..409f0057b2 100644 --- a/docs/build/old/android-notes.md +++ b/docs/build/old/android-notes.md @@ -8,13 +8,13 @@ WARNING: these are old configuration notes. See [index.md](../index.md) for curr * [Pandoc](#Install-Pandoc) for generating offline help ## Install Java -It is recommended to use openJDK because of oracle license issues. +It is recommended to use openJDK because of Oracle license issues. Tested with latest release for openJDK 8 from https://github.com/ojdkbuild/ojdkbuild 1. Download and unpack the zip archive -2. On on windows: use the default java path C:\Program Files\Java to avoid error message "Error 0x80010135 Path Too Long". -3. Aso set an environment variable for JAVA_HOME e.g C:\Program Files\Java\openjdk-1.8.0.232-1 +2. On windows: use the default java path C:\Program Files\Java to avoid error message "Error 0x80010135 Path Too Long". +3. Also set an environment variable for JAVA_HOME e.g C:\Program Files\Java\openjdk-1.8.0.232-1 ## Setup Android Studio @@ -22,16 +22,19 @@ https://github.com/ojdkbuild/ojdkbuild 2. For Windows users, set environment variable **ANDROID_HOME** to the location of your Android SDK. The default installation location is **C:\Users\\[USER]\AppData\Local\Android\sdk** where [USER] is your username. You may need to log out and log back in to take effect. For MacOS/Linux users, add the following to **~/.bashrc** or **~/.bash_profile** + ```bash export ANDROID_HOME=$HOME/Android/Sdk export PATH=$PATH:$ANDROID_HOME/tools ``` For MacOS users, add the following (adjusted appropriately) to **~/.bashrc** or **~/.bash_profile** if your Java version is too strange for gradlew to understand (e.g., 11.0.2) + ```bash export JAVA_HOME=$(/usr/libexec/java_home -v 1.8) echo $JAVA_HOME #should output: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home ``` 3. For Windows users, from a Git Bash Prompt window, cd to the **sdk/tools/bin** folder and accept all the SDK license agreements + ``` yes | ./sdkmanager.bat --licenses ``` diff --git a/docs/build/sentry-cli.md b/docs/build/sentry-cli.md index 968c42e3ae..7136bee2c1 100644 --- a/docs/build/sentry-cli.md +++ b/docs/build/sentry-cli.md @@ -1,14 +1,15 @@ # Setting up sentry-cli -Contact the Keyman team if you need access to sentry.keyman.com for development. -You will also need to install [sentry-cli](https://docs.sentry.io/cli/installation/) for uploading Debug symbols. +Contact the Keyman team if you need access to sentry.keyman.com for development. +You will also need to install [sentry-cli](https://docs.sentry.io/cli/installation/) for uploading Debug symbols. After setting up your personal [Auth token](http://sentry.keyman.com/settings/account/api/auth-tokens/), add the following to **~/.bashrc** ```bash export SENTRY_AUTH_TOKEN={your Sentry auth token} export SENTRY_URL=https://sentry.keyman.com export SENTRY_ORG=keyman +# select the appropriate project here: export SENTRY_PROJECT=keyman-android ``` -To validate your configuration, from the `android/` folder run `sentry-cli info`. +To validate your configuration, for example for keyman-android, from the `android/` folder run `sentry-cli info`. diff --git a/resources/devbox/macos/macos.sh b/resources/devbox/macos/macos.sh index 0ddc488443..a5e7232e46 100755 --- a/resources/devbox/macos/macos.sh +++ b/resources/devbox/macos/macos.sh @@ -26,11 +26,24 @@ PARAMFOUND=false function print_help() { echo "Usage: macos.sh targets" - echo " targets: one or more of: " - echo " android ios macos web all" + echo + echo "The targets parameter determines the platforms and components" + echo "that this script will setup your environment to build." + echo + echo " group targets:" + echo " all build all platform targets" + echo " all-optional build all targets, including optional" + echo + echo " platform targets:" + echo " android ios macos web" + echo echo " optional targets:" - echo " kmcomp pandoc sentry-cli" - echo "Targets will automatically include dependency targets." + echo " kmcomp Keyman keyboard compiler" + echo " pandoc Documentation compiler" + echo " sentry-cli sentry.keyman.com debug symbol uploader" + echo + echo "Note: If a target has dependencies, those will automatically" + echo " be included." } while [[ $# -gt 0 ]] ; do @@ -75,6 +88,16 @@ while [[ $# -gt 0 ]] ; do REQUIRE_WEB=true PARAMFOUND=true ;; + all-optional) + REQUIRE_ANDROID=true + REQUIRE_IOS=true + REQUIRE_MACOS=true + REQUIRE_WEB=true + REQUIRE_KMCOMP=true + REQUIRE_PANDOC=true + REQUIRE_SENTRYCLI=true + PARAMFOUND=true + ;; *) echo "Error: unrecognised parameter." echo @@ -86,7 +109,7 @@ while [[ $# -gt 0 ]] ; do done if ! $PARAMFOUND; then - echo "Error: must have a target parameter" + echo "Error: must specify target parameter" echo print_help exit 1 @@ -95,7 +118,7 @@ fi # This script will configure your development environment from a bare metal mac install. It should be idempotent. echo "This script will configure your macOS computer to build Keyman, installing build tools and prerequisites." -echo "You can also do this yourself following the notes in building.md." +echo "You can also do this yourself following the notes in docs/build/macos.md." echo read -p "Press ENTER to start install" @@ -103,7 +126,7 @@ if $REQUIRE_IOS || $REQUIRE_ANDROID; then REQUIRE_WEB=true fi -if $REQUIRE_ANDROID; then +if $REQUIRE_IOS || $REQUIRE_MACOS || $REQUIRE_ANDROID; then REQUIRE_PANDOC=true fi @@ -192,6 +215,7 @@ if $REQUIRE_MACOS || $REQUIRE_IOS || $REQUIRE_ANDROID; then echo "additional components:" if $REQUIRE_MACOS || $REQUIRE_IOS; then echo " * XCode" + echo "You may need to run xcode-select to choose the correct version of XCode command line tools." fi if $REQUIRE_ANDROID; then echo " * Android Studio" From 49516853f9460be6115038521adde03172ea35aa Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 30 Jun 2021 14:01:19 -0400 Subject: [PATCH 79/81] auto: increment master version to 15.0.79 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- .../web/input-processor/package-lock.json | 2 +- common/core/web/input-processor/package.json | 16 +++++++-------- .../web/keyboard-processor/package-lock.json | 2 +- .../core/web/keyboard-processor/package.json | 10 +++++----- .../core/web/tools/recorder/package-lock.json | 2 +- common/core/web/tools/recorder/package.json | 10 +++++----- .../tools/sentry-manager/package-lock.json | 2 +- .../web/tools/sentry-manager/package.json | 4 ++-- common/core/web/utils/package-lock.json | 2 +- common/core/web/utils/package.json | 6 +++--- common/models/templates/package-lock.json | 2 +- common/models/templates/package.json | 8 ++++---- common/models/types/package-lock.json | 2 +- common/models/types/package.json | 2 +- common/models/wordbreakers/package-lock.json | 2 +- common/models/wordbreakers/package.json | 4 ++-- common/predictive-text/package-lock.json | 2 +- common/predictive-text/package.json | 14 ++++++------- developer/js/package-lock.json | 2 +- developer/js/package.json | 8 ++++---- lerna.json | 2 +- resources/gosh/package-lock.json | 2 +- resources/gosh/package.json | 2 +- resources/web-environment/package-lock.json | 2 +- resources/web-environment/package.json | 4 ++-- web/package-lock.json | 2 +- web/package.json | 20 +++++++++---------- 29 files changed, 73 insertions(+), 69 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3179b65fe3..008f986960 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 15.0.78 alpha 2021-06-30 + +* fix(web): console errors when using SHIFT w help-text 'osk' (#5392) + ## 15.0.77 alpha 2021-06-29 * fix(windows): #5336 appcontext get to handle small buffer (#5383) diff --git a/VERSION.md b/VERSION.md index 52c9161411..adb90c6b70 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.78 \ No newline at end of file +15.0.79 \ No newline at end of file diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 157d3314c2..cdb898d011 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index 8b1ee36ba6..772cffb71b 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.78", + "version": "15.0.79", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.78", - "@keymanapp/resources-gosh": "^15.0.78", + "@keymanapp/lexical-model-compiler": "^15.0.79", + "@keymanapp/resources-gosh": "^15.0.79", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.78", - "@keymanapp/lexical-model-layer": "^15.0.78", - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78", + "@keymanapp/keyboard-processor": "^15.0.79", + "@keymanapp/lexical-model-layer": "^15.0.79", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 700809350f..896fdff38b 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 4d2e1d9672..c720b3bcb1 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.78", + "version": "15.0.79", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.78", + "@keymanapp/resources-gosh": "^15.0.79", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79", "@types/node": "^11.9.4" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index 47a01b36d7..4126c265b2 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 088861d40c..bb0ea50430 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.78", + "version": "15.0.79", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.78", - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78" + "@keymanapp/keyboard-processor": "^15.0.79", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79" }, "devDependencies": { "typescript": "^3.8.3" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index dc1a750683..d3fedcac22 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index 925bf6792b..8d4c4ae07e 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.78", + "version": "15.0.79", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/web-environment": "^15.0.79", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index d2c9edacaa..973839ab33 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index 1bde8f815c..56ba82dfcd 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.78", + "version": "15.0.79", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", + "@keymanapp/resources-gosh": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 2b2dbc89aa..a9f880510e 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index 89b8e2987c..8fbd68b6ea 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.78", + "version": "15.0.79", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.78" + "@keymanapp/models-wordbreakers": "^15.0.79" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index cd88c2efd9..021d3041de 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index a184ea1f40..772f892ccb 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.78", + "version": "15.0.79", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index d3a4273ba4..a3e130991b 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 4d989ffd3c..3ca42e32fd 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.78", + "version": "15.0.79", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.78", + "@keymanapp/models-types": "^15.0.79", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 0cdcf495d4..609c6703f1 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index c0c707471a..c8589e8520 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.78", + "version": "15.0.79", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/resources-gosh": "^15.0.78", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/resources-gosh": "^15.0.79", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.2", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.78", - "@keymanapp/models-wordbreakers": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78", + "@keymanapp/models-templates": "^15.0.79", + "@keymanapp/models-wordbreakers": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index a6f3e142a6..05116874f9 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index 9b971ea4e3..8981db6150 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.78", + "version": "15.0.79", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.78", + "@keymanapp/models-types": "^15.0.79", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.78", - "@keymanapp/models-wordbreakers": "^15.0.78", + "@keymanapp/models-templates": "^15.0.79", + "@keymanapp/models-wordbreakers": "^15.0.79", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/lerna.json b/lerna.json index eb849b2e38..a024675af1 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.78" + "version": "15.0.79" } diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index 0563da842d..d54cc292f0 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index f1cba0a468..d5c8d68fc2 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.78", + "version": "15.0.79", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index a746a8f6c6..6d763ccf58 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index f46776e58b..a66717cce1 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.78", + "version": "15.0.79", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.78" + "@keymanapp/resources-gosh": "^15.0.79" } } diff --git a/web/package-lock.json b/web/package-lock.json index b8819e53d4..ad6eeaa906 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.78", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index 20f1ff2965..d21c44d729 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.78", + "version": "15.0.79", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.78", - "@keymanapp/web-sentry-manager": "^15.0.78", + "@keymanapp/resources-gosh": "^15.0.79", + "@keymanapp/web-sentry-manager": "^15.0.79", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.78", - "@keymanapp/keyboard-processor": "^15.0.78", - "@keymanapp/lexical-model-layer": "^15.0.78", - "@keymanapp/models-types": "^15.0.78", - "@keymanapp/recorder-core": "^15.0.78", - "@keymanapp/web-environment": "^15.0.78", - "@keymanapp/web-utils": "^15.0.78", + "@keymanapp/input-processor": "^15.0.79", + "@keymanapp/keyboard-processor": "^15.0.79", + "@keymanapp/lexical-model-layer": "^15.0.79", + "@keymanapp/models-types": "^15.0.79", + "@keymanapp/recorder-core": "^15.0.79", + "@keymanapp/web-environment": "^15.0.79", + "@keymanapp/web-utils": "^15.0.79", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" From 449083fc0a244e9bd2bb889c5c6187cb9ee89f2d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jul 2021 07:39:39 +1000 Subject: [PATCH 80/81] chore(developer): remove obsolete InitClasses --- windows/src/developer/TIKE/Tike.dpr | 1 - windows/src/developer/TIKE/main/UfrmMain.pas | 24 ++++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/windows/src/developer/TIKE/Tike.dpr b/windows/src/developer/TIKE/Tike.dpr index 3977f72f49..316e85f283 100644 --- a/windows/src/developer/TIKE/Tike.dpr +++ b/windows/src/developer/TIKE/Tike.dpr @@ -317,7 +317,6 @@ begin Application.Title := 'Keyman Developer'; //TBX.TBXSetTheme('OfficeXP2'); if TikeActive then Exit; - InitClasses; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); Application.Run; diff --git a/windows/src/developer/TIKE/main/UfrmMain.pas b/windows/src/developer/TIKE/main/UfrmMain.pas index 5185a4b927..30e4d9e9ff 100644 --- a/windows/src/developer/TIKE/main/UfrmMain.pas +++ b/windows/src/developer/TIKE/main/UfrmMain.pas @@ -1,18 +1,18 @@ (* Name: UfrmMain Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 20 Jun 2006 Modified Date: 24 Aug 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 20 Jun 2006 - mcdurdin - Initial version 01 Aug 2006 - mcdurdin - Rework ui using sp-TBX 02 Aug 2006 - mcdurdin - Remove old menu @@ -410,15 +410,12 @@ type var frmKeymanDeveloper: TfrmKeymanDeveloper; -procedure InitClasses; - implementation uses System.Math, Winapi.WinInet, Winapi.ShlObj, - Winapi.Urlmon, Winapi.UxTheme, System.Win.ComObj, Vcl.Themes, @@ -1544,12 +1541,5 @@ begin ProjectForm.SetGlobalProject; end; -procedure InitClasses; // I3350 -const - CUserAgent: AnsiString = 'Mozilla/5.0 (compatible; MSIE 11.0; Windows NT 6.1; WOW64; Trident/5.0; TIKE/'+SKeymanVersion+')'; // I4045 -begin - OleCheck(UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, PAnsiChar(CUserAgent), Length(CUserAgent), 0)); // I4045 -end; - end. From b4e6055d29b946ee45d9b8ed79c55c0641943716 Mon Sep 17 00:00:00 2001 From: jahorton Date: Thu, 1 Jul 2021 08:24:40 +0700 Subject: [PATCH 81/81] change(web): fat-finger-performance unit-test threshold --- .../input-processor/tests/cases/inputProcessor.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/common/core/web/input-processor/tests/cases/inputProcessor.js b/common/core/web/input-processor/tests/cases/inputProcessor.js index 3344949a0f..ef4d7e364b 100644 --- a/common/core/web/input-processor/tests/cases/inputProcessor.js +++ b/common/core/web/input-processor/tests/cases/inputProcessor.js @@ -103,7 +103,9 @@ describe('InputProcessor', function() { // Assumes no SMP chars in the source, which is fine. let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength()); - this.timeout(250); // 250 ms, excluding text import. + this.timeout(500); // 500 ms, excluding text import. + // These often run on VMs, so we'll be a bit generous. + let core = new InputProcessor(); // I mean, it IS long context, and time // thresholding is disabled within Node. @@ -137,8 +139,13 @@ describe('InputProcessor', function() { // Assumes no SMP chars in the source, which is fine. let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength()); - this.timeout(250); // 250 ms, excluding text import. - let core = new InputProcessor(); // I mean, it IS long context, and time + this.timeout(500); // 500 ms, excluding text import. + // These often run on VMs, so we'll be a bit generous. + // + // Keep at the same 'order of magnitude' as the + // 'without fat-fingers' test. + + let core = new InputProcessor(); // It IS long context, and time // thresholding is disabled within Node. core.activeKeyboard = keyboard;