Merge branch 'master' into change/web/context-tracker-backspaces-and-alignment

This commit is contained in:
Joshua Horton 2025-01-17 10:14:52 +07:00
commit b314cee54b
76 changed files with 3931 additions and 469 deletions

View file

@ -1,5 +1,22 @@
# Keyman Version History
## 18.0.169 alpha 2025-01-17
* chore(windows): remove `postinstall` state from mermaid diagram (#12923)
## 18.0.168 alpha 2025-01-16
* chore: update macOS environment-variable shell script (#12878)
* fix(android): use main looper to dispatch key events when OSK is hidden (#12871)
* chore(web): integrates predictive-text builds to top level script, reconnects headless tests (#12866)
* chore(ios): Update crowdin strings for Khmer (#12910)
* chore(windows): merge master epic windows updates (#12904)
* fix(developer): detect invalid key ids in touch layout files (#12895)
* chore: use GitHub PR titles when writing HISTORY.md (#12907)
* fix(developer): filter incorrect fonts out of .keyboard_info (#12909)
* change(web): make 'keep' transform pattern match standard suggestion pattern by including the prefix string (#12906)
* chore: Add docker images for building the different platforms (#11397)
## 18.0.167 alpha 2025-01-15
* chore: changes to use https and removes anchor in docs (#12838)
@ -213,7 +230,7 @@
## 18.0.134 alpha 2024-11-04
* (#12606)
* fix(developer): ldml don't allow a uset as right-hand-side variable (#12606)
## 18.0.133 alpha 2024-11-01

View file

@ -1 +1 @@
18.0.168
18.0.170

View file

@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@ -70,6 +71,10 @@ final class KMKeyboard extends WebView {
protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED;
protected ArrayList<String> javascriptAfterLoad = new ArrayList<>();
// .getMainLooper() returns the looper associated with the main UI thread.
// https://stackoverflow.com/questions/13974661/runonuithread-vs-looper-getmainlooper-post-in-android
private Handler jsQueuer = new Handler(Looper.getMainLooper());
private static String currentKeyboard = null;
/**
@ -368,7 +373,7 @@ final class KMKeyboard extends WebView {
if(this.javascriptAfterLoad.size() > 0) {
// Don't call this WebView method on just ANY thread - run it on the main UI thread.
// https://stackoverflow.com/a/22611010
this.postDelayed(new Runnable() {
jsQueuer.postDelayed(new Runnable() {
@Override
public void run() {
StringBuilder allCalls = new StringBuilder();

View file

@ -0,0 +1,52 @@
{
Keyman is copyright (C) SIL Global. MIT License.
This module provides functionality to track the execution state of the Keyman
engine. It uses a global atom to record whether Keyman has started during the
current session and checks if it has previously run.
}
unit Keyman.System.ExecutionHistory;
interface
const
AtomName = 'KeymanSessionFlag';
function RecordKeymanStarted : Boolean;
function HasKeymanRun : Boolean;
implementation
uses
System.SysUtils,
Winapi.Windows,
KLog;
function RecordKeymanStarted : Boolean;
var
atom: WORD;
begin
atom := GlobalAddAtom(AtomName);
if atom = 0 then
begin
// TODO-WINDOWS-UPDATES: #10210 log to sentry
Result := False;
end
else
Result := True;
end;
function HasKeymanRun : Boolean;
begin
Result := GlobalFindAtom(AtomName) <> 0;
if not Result then
begin
if GetLastError <> ERROR_FILE_NOT_FOUND then
begin
// TODO-WINDOWS-UPDATES: log to Sentry
end;
end;
end;
end.

View file

@ -35,6 +35,7 @@ type
TUpdateCheckResponse = record
private
FOriginalData: string;
FInstallSize: Int64;
FInstallURL: string;
FNewVersion: string;
@ -46,9 +47,13 @@ type
FFileName: string;
function ParseKeyboards(nodes: TJSONObject): Boolean;
function ParseLanguages(i: Integer; v: TJSONValue): Boolean;
function DoParse(const message, app, currentVersion: string): Boolean;
public
function Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
procedure SaveToFile(const Filename: string);
function LoadFromFile(const Filename, app, currentVersion: string): Boolean;
property CurrentVersion: string read FCurrentVersion;
property NewVersion: string read FNewVersion;
property NewVersionWithTag: string read FNewVersionWithTag;
@ -58,25 +63,32 @@ type
property ErrorMessage: string read FErrorMessage;
property Status: TUpdateCheckResponseStatus read FStatus;
property Packages: TUpdateCheckResponsePackages read FPackages;
property OriginalData: string read FOriginalData;
end;
implementation
uses
System.Classes,
System.Generics.Collections,
versioninfo;
{ TUpdateCheckResponse }
function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
begin
Result := DoParse(string(UTF8String(message)), app, currentVersion);
end;
function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean;
var
node, doc: TJSONObject;
begin
FOriginalData := message;
FCurrentVersion := currentVersion;
FStatus := ucrsNoUpdate;
// TODO: test with UTF8 characters in response
doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject;
doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject;
if doc = nil then
begin
FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]);
@ -168,4 +180,29 @@ begin
Result := True;
end;
function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean;
var
ss: TStringStream;
begin
ss := TStringStream.Create('', TEncoding.UTF8);
try
ss.LoadFromFile(Filename);
Result := DoParse(ss.DataString, app, currentVersion);
finally
ss.Free;
end;
end;
procedure TUpdateCheckResponse.SaveToFile(const Filename: string);
var
ss: TStringStream;
begin
ss := TStringStream.Create(FOriginalData, TEncoding.UTF8);
try
ss.SaveToFile(Filename);
finally
ss.Free;
end;
end;
end.

View file

@ -17,6 +17,8 @@ type
const S_CEF_SubProcess = 'kmbrowserhost.exe';
const S_CEF_SubProcess_Developer = 'kmdbrowserhost.exe';
const S_CustomisationFilename = 'desktop_pro.pxx';
const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\';
public
const S_KMShell = 'kmshell.exe';
const S_TSysInfoExe = 'tsysinfo.exe';
@ -27,8 +29,10 @@ type
const S_FallbackKeyboardPath = 'Keyboards\';
const S__Package = '_Package\';
const S_MCompileExe = 'mcompile.exe';
const S_UpdateCache_Metadata = 'cache.json';
class function ErrorLogPath(const app: string = ''): string; static;
class function KeymanHelpPath(const HelpFile: string): string; static;
class function KeymanUpdateCachePath(const filename: string = ''): string; static;
class function KeymanDesktopInstallPath(const filename: string = ''): string; static;
class function KeymanEngineInstallPath(const filename: string = ''): string; static;
class function KeymanDesktopInstallDir: string; static;
@ -423,6 +427,11 @@ begin
Result := '';
end;
class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string;
begin
Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename;
end;
class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
begin
// On developer machines, if we are running within the source repo, then use

View file

@ -175,8 +175,10 @@ const
SRegValue_CharMapSourceData = 'charmap source data'; // LM
SRegValue_AvailableLanguages = 'available languages'; //CU
SRegValue_CurrentLanguage = 'current language'; //CU
SRegValue_AvailableLanguages = 'available languages'; // CU
SRegValue_CurrentLanguage = 'current language'; // CU
SRegValue_Update_State = 'update state'; // CU
{ Privacy }
@ -312,8 +314,10 @@ const
SRegValue_ActiveProject_Filename = 'project filename';
SRegValue_ActiveProject_SourcePath = 'source path';
SRegValue_AutomaticUpdates = 'automatic updates'; //CU
SRegValue_CheckForUpdates = 'check for updates'; // CU
SRegValue_LastUpdateCheckTime = 'last update check time'; // CU
SRegValue_ApplyNow = 'apply now'; // CU Start the install now even though it will require an restart
SRegValue_UpdateCheck_UseProxy = 'update check use proxy'; // CU
SRegValue_UpdateCheck_ProxyHost = 'update check proxy host'; // CU

View file

@ -56,10 +56,8 @@ TEST_F(KmCoreKeyboardApiTests, LoadFromBlobNull) {
// Setup
km::core::path kmxfile = "";
std::unique_ptr<uint8_t> data(new uint8_t[0]);
// Execute
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.get(), 0, &this->keyboard);
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), nullptr, 0, &this->keyboard);
// Verify
EXPECT_EQ(status, KM_CORE_STATUS_INVALID_ARGUMENT);

View file

@ -499,9 +499,6 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
keyboard_info.languages[language] = {};
}
const fontSource = [].concat(...kmpJsonData.keyboards.map(e => e.displayFont ? [e.displayFont] : []), ...kmpJsonData.keyboards.map(e => e.webDisplayFonts ?? []));
const oskFontSource = [].concat(...kmpJsonData.keyboards.map(e => e.oskFont ? [e.oskFont] : []), ...kmpJsonData.keyboards.map(e => e.webOskFonts ?? []));
let commonScript = null;
for(const bcp47 of Object.keys(keyboard_info.languages)) {
@ -528,6 +525,20 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
// do it right now.
//
// The code below:
// 1. Only includes fonts associated with keyboards which support the current bcp47 (filter)
// 2. Joins the displayFont and webDisplayFonts data, and removes duplicates (...new Set())
const supportedKeyboards = kmpJsonData.keyboards.filter(k => k.languages.find(lang => lang.id == bcp47));
const fontSource = [...new Set([].concat(
...supportedKeyboards.map(e => e.displayFont ? [e.displayFont] : []),
...supportedKeyboards.map(e => e.webDisplayFonts ?? [])
))];
const oskFontSource = [...new Set([].concat(
...supportedKeyboards.map(e => e.oskFont ? [e.oskFont] : []),
...supportedKeyboards.map(e => e.webOskFonts ?? [])
))];
if(fontSource.length) {
language.font = await this.fontSourceToKeyboardInfoFont(kpsFilename, kmpJsonData, fontSource);
if(language.font == null) {

View file

@ -46,6 +46,16 @@ function GetKeyIdUnicodeType(value: string): TKeyIdType {
function KeyIdType(FId: string): TKeyIdType { // I4142
FId = FId.toUpperCase();
// Validate key id format:
// K_xxxx -- predefined virtual key - restricted character set
// T_xxxx -- custom 'touch' virtual key - touch key id
// U_ABCD_1234 -- Unicode key id (1+ chars)
// x00 -- "ISO" key identifier (not currently supported in touch layout files)
if(!/^((K_[A-Z0-9_?]+)|(T_\S+)|(U_[0-9A-F_]+))$/.test(FId)) {
// note: |[A-Z][0-9][0-9] -- ISO key identifiers not currently supported
return TKeyIdType.Key_Invalid;
}
switch(FId.charAt(0)) {
case 'T':
return TKeyIdType.Key_Touch;

View file

@ -0,0 +1,550 @@
{
"tablet": {
"displayUnderlying": false,
"layer": [
{
"id": "default",
"row": [
{
"id": 1,
"key": [
{
"id": "U_1E6B[_0307]",
"text": "១"
},
{
"id": "K_2",
"text": "២"
},
{
"id": "K_3",
"text": "៣"
},
{
"id": "K_4",
"text": "៤"
},
{
"id": "K_5",
"text": "៥"
},
{
"id": "K_6",
"text": "៦"
},
{
"id": "K_7",
"text": "៧"
},
{
"id": "K_8",
"text": "៨"
},
{
"id": "K_9",
"text": "៩"
},
{
"id": "K_0",
"text": "០"
},
{
"id": "K_HYPHEN",
"text": "ឥ"
},
{
"id": "K_EQUAL",
"text": "ឲ"
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"width": "100",
"sp": "1"
}
]
},
{
"id": 2,
"key": [
{
"id": "K_Q",
"text": "ឆ",
"pad": "75"
},
{
"id": "K_W",
"text": ""
},
{
"id": "K_E",
"text": ""
},
{
"id": "K_R",
"text": "រ"
},
{
"id": "K_T",
"text": "ត"
},
{
"id": "K_Y",
"text": "យ"
},
{
"id": "K_U",
"text": ""
},
{
"id": "K_I",
"text": ""
},
{
"id": "K_O",
"text": ""
},
{
"id": "K_P",
"text": "ផ"
},
{
"id": "K_LBRKT",
"text": ""
},
{
"id": "K_RBRKT",
"text": "ឪ"
},
{
"id": "T_new_138",
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 3,
"key": [
{
"id": "K_BKQUOTE",
"text": "«"
},
{
"id": "K_A",
"text": ""
},
{
"id": "K_S",
"text": "ស"
},
{
"id": "K_D",
"text": "ដ"
},
{
"id": "K_F",
"text": "ថ"
},
{
"id": "K_G",
"text": "ង"
},
{
"id": "K_H",
"text": "ហ"
},
{
"id": "K_J",
"text": ""
},
{
"id": "K_K",
"text": "ក"
},
{
"id": "K_L",
"text": "ល"
},
{
"id": "K_COLON",
"text": ""
},
{
"id": "K_QUOTE",
"text": ""
},
{
"id": "K_BKSLASH",
"text": "ឮ"
}
]
},
{
"id": 4,
"key": [
{
"id": "K_SHIFT",
"text": "*Shift*",
"width": "160",
"sp": "1",
"nextlayer": "shift"
},
{
"id": "K_oE2",
"text": ""
},
{
"id": "K_Z",
"text": "ឋ"
},
{
"id": "K_X",
"text": "ខ"
},
{
"id": "K_C",
"text": "ច"
},
{
"id": "K_V",
"text": "វ"
},
{
"id": "K_B",
"text": "ប"
},
{
"id": "K_N",
"text": "ន"
},
{
"id": "K_M",
"text": "ម"
},
{
"id": "K_COMMA",
"text": ""
},
{
"id": "K_PERIOD",
"text": "។"
},
{
"id": "K_SLASH",
"text": ""
},
{
"id": "T_new_164",
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 5,
"key": [
{
"id": "K_LCONTROL",
"text": "*AltGr*",
"width": "160",
"sp": "1"
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": "160",
"sp": "1"
},
{
"id": "K_SPACE",
"text": "",
"width": "930"
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": "160",
"sp": "1"
}
]
}
]
},
{
"id": "shift",
"row": [
{
"id": 1,
"key": [
{
"id": "K_1",
"text": "!"
},
{
"id": "K_2",
"text": "ៗ"
},
{
"id": "K_3",
"text": "\""
},
{
"id": "K_4",
"text": "៛"
},
{
"id": "K_5",
"text": "%"
},
{
"id": "K_6",
"text": ""
},
{
"id": "K_7",
"text": ""
},
{
"id": "K_8",
"text": ""
},
{
"id": "K_9",
"text": "("
},
{
"id": "K_0",
"text": ")"
},
{
"id": "K_HYPHEN",
"text": ""
},
{
"id": "K_EQUAL",
"text": "="
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"width": "100",
"sp": "1"
}
]
},
{
"id": 2,
"key": [
{
"id": "K_Q",
"text": "ឈ",
"pad": "75"
},
{
"id": "K_W",
"text": ""
},
{
"id": "K_E",
"text": ""
},
{
"id": "K_R",
"text": "ឬ"
},
{
"id": "K_T",
"text": "ទ"
},
{
"id": "K_Y",
"text": ""
},
{
"id": "K_U",
"text": ""
},
{
"id": "K_I",
"text": ""
},
{
"id": "K_O",
"text": ""
},
{
"id": "K_P",
"text": "ភ"
},
{
"id": "K_LBRKT",
"text": ""
},
{
"id": "K_RBRKT",
"text": "ឧ"
},
{
"id": "T_new_364",
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 3,
"key": [
{
"id": "K_BKQUOTE",
"text": "»"
},
{
"id": "K_A",
"text": ""
},
{
"id": "K_S",
"text": ""
},
{
"id": "K_D",
"text": "ឌ"
},
{
"id": "K_F",
"text": "ធ"
},
{
"id": "K_G",
"text": "អ"
},
{
"id": "K_H",
"text": "ះ"
},
{
"id": "K_J",
"text": "ញ"
},
{
"id": "K_K",
"text": "គ"
},
{
"id": "K_L",
"text": "ឡ"
},
{
"id": "K_COLON",
"text": ""
},
{
"id": "K_QUOTE",
"text": ""
},
{
"id": "K_BKSLASH",
"text": "ឭ"
}
]
},
{
"id": 4,
"key": [
{
"id": "K_SHIFT",
"text": "*Shift*",
"width": "160",
"sp": "2",
"nextlayer": "default"
},
{
"id": "K_oE2",
"text": ""
},
{
"id": "K_Z",
"text": "ឍ"
},
{
"id": "K_X",
"text": "ឃ"
},
{
"id": "K_C",
"text": "ជ"
},
{
"id": "K_V",
"text": ""
},
{
"id": "K_B",
"text": "ព"
},
{
"id": "K_N",
"text": "ណ"
},
{
"id": "K_M",
"text": ""
},
{
"id": "K_COMMA",
"text": ""
},
{
"id": "K_PERIOD",
"text": "៕"
},
{
"id": "K_SLASH",
"text": "?"
},
{
"id": "T_new_390",
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 5,
"key": [
{
"id": "K_LCONTROL",
"text": "*AltGr*",
"width": "160",
"sp": "1"
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": "160",
"sp": "1"
},
{
"id": "K_SPACE",
"text": "",
"width": "930"
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": "160",
"sp": "1"
}
]
}
]
}
],
"font": "Arial"
}
}

View file

@ -0,0 +1,10 @@
store(&VERSION) '15.0'
store(&NAME) "error_touch_layout_invalid_identifier"
store(&COPYRIGHT) '© 2015-2024 SIL Global'
store(&TARGETS) 'any'
store(&LAYOUTFILE) 'error_touch_layout_invalid_identifier.keyman-touch-layout'
store(&KEYBOARDVERSION) '1.3'
begin Unicode > use(main)
group(main) using keys

View file

@ -36,7 +36,9 @@ describe('KeymanWeb Compiler', function() {
});
this.afterEach(function() {
callbacks.printMessages();
if(this.currentTest?.isFailed() || debug) {
callbacks.printMessages();
}
callbacks.clear();
});
@ -203,6 +205,19 @@ describe('KeymanWeb Compiler', function() {
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel));
});
it('should give error ERROR_TouchLayoutInvalidIdentifier if a virtual key is badly formatted e.g. U_1234[_5678]', async function() {
// #12870
const filenames = generateTestFilenames('error_touch_layout_invalid_identifier');
let result = await kmnCompiler.run(filenames.source, null);
assert.isNull(result);
assert.isFalse(callbacks.hasMessage(KmnCompilerMessages.INFO_MinimumCoreEngineVersion));
assert.isFalse(callbacks.hasMessage(KmwCompilerMessages.INFO_MinimumWebEngineVersion));
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_TouchLayoutInvalidIdentifier));
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_InvalidTouchLayoutFile));
assert.lengthOf(callbacks.messages, 2);
});
});
async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):

View file

@ -238,83 +238,25 @@ Android projects. `JAVA_HOME_11` is mostly used by CI.
## Docker Builder
The Docker builder allows you to perform a build from anywhere Docker is supported.
To build the docker image:
```shell
cd linux
docker pull ubuntu:latest # (to make sure you have an up-to-date image)
docker build . -t keymanapp/keyman-linux-builder:latest
```
Once the image is built, it may be used to build parts of Keyman.
**Note** that it's not yet possible to run tests in the Docker container.
- core
```shell
# build 'Keyman Core' in docker
# keep linux build artifacts separate
mkdir -p $(git rev-parse --show-toplevel)/core/build/linux
docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \
-v $(git rev-parse --show-toplevel)/core/build/linux:/home/build/build/core/build \
keymanapp/keyman-linux-builder:latest \
core/build.sh --debug
```
- linux
```shell
# build 'Keyman for Linux' installation in docker
docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \
--entrypoint /bin/bash keymanapp/keyman-linux-builder:latest \
-c 'DESTDIR=/home/build /usr/bin/bashwrapper linux/build.sh --debug build install'
```
- Keyman Web
```shell
# build 'Keyman Web' in docker
docker run --privileged -it --rm \
-v $(git rev-parse --show-toplevel):/home/build/build \
keymanapp/keyman-linux-builder:latest \
web/build.sh --debug
```
- Keyman for Android
```shell
# build 'Keyman for Android' in docker
docker run -it --rm -v $(git rev-parse --show-toplevel):/home/build/build \
keymanapp/keyman-linux-builder:latest \
android/build.sh --debug
```
### Customizing the builder
You can use Docker [build args](https://docs.docker.com/build/guide/build-args/) to customize the image build. As an example, the following will build an image explicitly with Ubuntu 23.04 and Node.js 20. Check the [Dockerfile](../../linux/Dockerfile) for `ARG` entries.
```shell
cd linux
docker pull ubuntu:23.04 # (to make sure you have an up-to-date image)
docker build . -t keymanapp/keyman-linux-builder:u23.04-node20 --build-arg OS_VERSION=23.04 --build-arg NODE_MAJOR=20
````
See [this README.md](../../resources/docker-images/README.md) for details.
### Using the builder with VSCode [Dev Containers](https://code.visualstudio.com/docs/devcontainers/tutorial)
1. Save the following as `.devcontainer/devcontainer.json`, updating the `image` to match the Docker image built above.
1. Save the following as `.devcontainer/devcontainer.json`, updating the `image`
to match the Docker image built above.
```json
// file: .devcontainer/devcontainer.json
{
"name": "Keyman Ubuntu 23.04",
"image": "keymanapp/keyman-linux-builder:u23.04-node18"
}
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
```
```json
// file: .devcontainer/devcontainer.json
{
"name": "Keyman Ubuntu 23.04",
"image": "keymanapp/keyman-linux-builder:u23.04-node18"
}
// For format details, see https://aka.ms/devcontainer.json. For config options,
// see the README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
```
2. in VSCode, use the "Dev Containers: Open Folder In Container…" option and choose the Keyman directory.
2. in VSCode, use the "Dev Containers: Open Folder In Container…" option and
choose the Keyman directory.
3. You will be given a window which is running VSCode inside this builder image, regardless of your host OS.
3. You will be given a window which is running VSCode inside this builder
image, regardless of your host OS.

View file

@ -67,6 +67,7 @@ https://help.keyman.com/developer/engine/android/latest-version/
| KEYMAN_MIN_VERSION_NPM | 10.5.1 |
| KEYMAN_MIN_VERSION_VISUAL_STUDIO | 2019 |
| KEYMAN_VERSION_CLDR | 45 |
| KEYMAN_VERSION_GRADLE | 7.6.4 |
| KEYMAN_VERSION_ICU | 73.1 |
| KEYMAN_VERSION_ISO639_3 | 2024-05-22 |
| KEYMAN_VERSION_JAVA | 11 |

View file

@ -226,6 +226,24 @@
/* Text showing name of spacebar caption - language + keyboard */
"menu-settings-spacebar-item-languageKeyboard" = "ភាសា និងក្ដារចុច";
/* Label for the "Adjust Keyboard Height" item on the main settings screen */
"menu-settings-adjust-keyboard-height" = "កែកម្ពស់ក្ដារចុច";
/* Title for the "Adjust Keyboard Height" settings child screen */
"adjust-keyboard-height-title" = "កែក្ដារចុច";
/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */
"button-label-reset-default-keyboard-height" = "ប្ដូរ​កម្ពស់ក្ដារចុចទៅលំនាំដើម";
/* Instruction text to drag keyboard to resize */
"keyboard-drag-instructions" = "រំកិលព្រួញដើម្បីកែកម្ពស់ក្ដារចុច";
/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */
"portrait-keyboard-rotate-instructions" = "បង្វិលឧបករណ៍ដើម្បីប្តូរទៅជាផ្ដេក";
/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */
"landscape-keyboard-rotate-instructions" = "បង្វិលឧបករណ៍ដើម្បីប្តូរទៅជាបញ្ឈរ";
/* Short text for notification: download failure for keyboard */
"notification-download-failure-keyboard" = "មិន​អាច​ទាញ​យក​ក្ដារចុច​បាន​ទេ";

View file

@ -1,90 +0,0 @@
# Copyright (c) 2022-2023 SIL Global. All rights reserved.
#
# builder image for a linux build
# see ../docs/build/linux-ubuntu.md
ARG OS_VERSION=latest
ARG OS_PLATFORM=amd64
FROM --platform=${OS_PLATFORM} ubuntu:${OS_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Linux Build Image"
# We will switch to a build user after some installation
USER root
ENV HOME /home/build
RUN useradd -c "Build user" --home-dir $HOME --create-home --shell /usr/bin/bashwrapper build
VOLUME /home/build/build
WORKDIR /home/build/build
ENV DEBIAN_FRONTEND noninteractive
ENV DEBIAN_PRIORITY critical
ENV DEBCONF_NOWARNINGS yes
# Update to the latest
RUN apt-get -q -y update && \
apt-get -q -y install devscripts equivs meson python3 python3-setuptools software-properties-common curl && \
add-apt-repository ppa:keymanapp/keyman && \
add-apt-repository ppa:keymanapp/keyman-alpha
RUN apt-get -q -y update && \
apt-get -q -y upgrade
# Install dependencies
ADD debian/control /tmp/control
# Answer 'yes' to install questions
RUN (yes | mk-build-deps --install /tmp/control) || true && \
rm /tmp/control
# Install Node
ARG NODE_MAJOR=18
RUN apt-get install -q -y ca-certificates curl gnupg && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"${NODE_MAJOR}".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y
ARG EMSCRIPTEN_VERSION=3.1.44
# Install emscripten
RUN cd /usr/share && \
git clone https://github.com/emscripten-core/emsdk.git && \
cd emsdk && \
./emsdk install ${EMSCRIPTEN_VERSION} && \
./emsdk activate ${EMSCRIPTEN_VERSION} && \
echo "#!/bin/bash" > /usr/bin/bashwrapper && \
echo "export EMSCRIPTEN_BASE=/usr/share/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper
# Keyman Web
RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get -q -y install ./google-chrome-stable_current_amd64.deb && \
rm google-chrome-stable_current_amd64.deb && \
echo "export CHROME_BIN=/opt/google/chrome/chrome" >> /usr/bin/bashwrapper
# Keyman for Android
RUN apt-get -q -y install gradle maven pandoc sdkmanager jq && \
sdkmanager platform-tools && \
yes | sdkmanager --licenses && \
chown -R build:build /opt/android-sdk/ && \
echo "export ANDROID_HOME=/opt/android-sdk" >> /usr/bin/bashwrapper && \
echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64" >> /usr/bin/bashwrapper
# Finish bashwrapper script and adjust permissions
RUN echo "\${@:-bash}" >> /usr/bin/bashwrapper && \
chmod +x /usr/bin/bashwrapper && \
chown -R build:build $HOME
# now, switch to build user
USER build
# Pre-install gradle. This will put files in ~/.gradle which will speed up builds.
RUN mkdir -p $HOME/tmp/gradle/wrapper && \
# KMEA uses gradle-7.5.1-bin
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.properties && \
curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradlew && \
chmod +x $HOME/tmp/gradlew && \
$HOME/tmp/gradlew --quiet && \
# Some projects use gradle-7.5.1-all, so we pre-install that as well
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.jar && \
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties && \
curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradlew && \
chmod +x $HOME/tmp/gradlew && \
$HOME/tmp/gradlew --quiet && \
rm -rf $HOME/tmp
ENTRYPOINT [ "/usr/bin/bashwrapper" ]

View file

@ -388,6 +388,11 @@
<!-- Introduced: 7.0.230.0 -->
<string name="koShowWelcome" comment="Startup options - show/hide welcome screen">Show welcome screen</string>
<!-- Context: Configuration Dialog - Options tab -->
<!-- String Type: FormatString -->
<!-- Introduced: 18.0.96.0 -->
<string name="koAutomaticUpdate" comment="Automatically download updates in the background, for installation later">Automatically download updates in the background, for installation later</string>
<!-- Context: Configuration Dialog - Options tab -->
<!-- String Type: FormatString -->
<!-- Introduced: 7.0.230.0 -->

View file

@ -86,13 +86,12 @@ This somewhat unwieldy incantation handles all our build environments.
The intent is to get a good solid consistent path for the script so that we can
safely include the build script, no matter what `pwd` is when the script is run.
The only modification permissible in this block is the
`<relative-path-to-repo-root>` text which will be a series of `../` paths taking
us to the repository root from the location of the script itself.
It is essential to make the include relative to the repo root, even for scripts
under the resources/ folder. Doing this gives us significant performance
under the `resources/` folder. Doing this gives us significant performance
benefits.
Inclusion of other scripts should be kept outside this standard build script
@ -1117,4 +1116,4 @@ Note: it is recommended that you use `$(builder_term text)` instead of
[`builder_echo`]: #builderecho-function
[`builder_die`]: #builderdie-function
[`builder_echo_debug`]: #builderechodebug-function
[`builder_is_debug_build`]: #builderisdebugbuild-function
[`builder_is_debug_build`]: #builderisdebugbuild-function

View file

@ -104,9 +104,9 @@ echo "increment-version.sh: running resources/build/version"
pushd "$KEYMAN_ROOT"
ABORT=0
if [[ -z "$fromversion" ]]; then
node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE || ABORT=$?
node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --github-pr || ABORT=$?
else
node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --from "$fromversion" --to "$toversion" || ABORT=$?
node resources/build/version/build/src/index.js history version -t "$GITHUB_TOKEN" -b "$base" $HISTORY_FORCE --github-pr --from "$fromversion" --to "$toversion" || ABORT=$?
fi
if [[ $ABORT = 50 ]]; then
@ -179,7 +179,7 @@ if [ "$action" == "commit" ]; then
# In order to avoid potential git conflicts, we run the history collater
# again on the master HISTORY.md. Note that the script always exits 1 to
# indicate it hasn't updated VERSION.md. We could tweak that in the future.
node resources/build/version/lib/index.js history --no-write-github-comment -t "$GITHUB_TOKEN" -b "$base" || true
node resources/build/version/lib/index.js history --no-write-github-comment --github-pr -t "$GITHUB_TOKEN" -b "$base" || true
# If HISTORY.md has been updated, then we want to create a branch and push
# it for review

View file

@ -31,7 +31,8 @@ KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automati
KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019
KEYMAN_MIN_VERSION_MESON=1.0.0
KEYMAN_VERSION_ICU=73.1 # See /core/subprojects/icu-minimal.wrap
KEYMAN_VERSION_GRADLE=7.6.4 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties
KEYMAN_VERSION_ICU=73.1 # See /core/subprojects/icu-minimal.wrap
# Language and runtime versions
KEYMAN_VERSION_JAVA=11 # We're using Java/OpenJDK 11

View file

@ -184,7 +184,7 @@ export const sendCommentToPullRequestAndRelatedIssues = async (
*/
export const fixupHistory = async (
octokit: GitHub, base: string, force: boolean, writeGithubComment: boolean, from?: string, to?: string
octokit: GitHub, base: string, force: boolean, writeGithubComment: boolean, useGitHubPRInfo: boolean, from?: string, to?: string
): Promise<number> => {
//
@ -194,7 +194,7 @@ export const fixupHistory = async (
let pulls: PRInformation[] = [];
try {
pulls = await reportHistory(octokit, base, force, false, from, to);
pulls = await reportHistory(octokit, base, force, useGitHubPRInfo, from, to);
} catch(e) {
logWarning(String(e));
return -1;

View file

@ -108,7 +108,7 @@ const main = async (): Promise<void> => {
if(argv._.includes('history')) {
logInfo(`# Validating history for ${version}`);
changeCount = await fixupHistory(octokit, argv.base, argv.force, argv['write-github-comment'], argv.from, argv.to);
changeCount = await fixupHistory(octokit, argv.base, argv.force, argv['write-github-comment'], argv['github-pr'], argv.from, argv.to);
logInfo(`# ${changeCount} change(s) found for ${version}\n`);
}

View file

@ -0,0 +1,65 @@
# Container
Docker containers that can be used to build Keyman on the respective
platforms. They contain everything that a CI build agent needs to
build for the platform.
## Prerequisites
You'll need Docker Buildx installed to successfully be able to build the
container images. This is most easily achieved by installing the [official
Docker version](https://docs.docker.com/engine/install/ubuntu/).
Currently it is not possible to use Podman instead of Docker due to a number
of bugs and incompatibilities in the Podman implementation.
## Building the images
To build the docker images:
```shell
resources/docker-images/build.sh
```
By default this will create 64-bit images for building
Keyman Core, Keyman for Android, Keyman for Linux and
Keyman for Web. These images are based on the Ubuntu 24.04
with Node 20 and Emscripten 3.1.58 (for the exact versions,
see [`minimum-versions.inc.sh`](../build/minimum-versions.inc.sh))
and are named e.g. `keyman-core-ci:default`.
The versions can be changed, e.g.
```shell
resources/docker-images/build.sh --ubuntu-version jammy --node 20
```
This will create an image named e.g. `keyman-core-ci:jammy-node20`.
Once the image is built, it may be used to build parts of Keyman.
## Building locally
It is possible to build locally with these images:
```shell
# build 'Keyman Core' in docker
resources/docker-images/run.sh core -- core/build.sh --debug build
```
Note: For Core and Linux we put the generated binaries in a
container specific directory because they are platform dependent.
If you build both with Docker and directly with the build scripts, it is
advisable to run a `git clean -dxf` before switching between the two. The
reason is that the Docker images use a different user, so that paths
will be different.
## Running tests locally
To run the tests locally, use the `run.sh` script:
```shell
# Run common/web tests
resources/docker-images/run.sh web -- common/web/build.sh --debug test
```

View file

@ -0,0 +1,68 @@
# Keyman is copyright (C) SIL Global. MIT License.
ARG BASE_VERSION=default
FROM keymanapp/keyman-base-ci:${BASE_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Android Build Image"
# Keyman for Android
SHELL ["/bin/bash", "-c"]
# Starting with Ubuntu 24.04 sdkmanager is no longer available, instead
# a version dependent package allows to install the cmdline tools
ARG JAVA_VERSION=11
RUN <<EOF
OS_VER=$(lsb_release -r -s 2>/dev/null)
echo "OS_VER=${OS_VER}"
if (( ${OS_VER%%.*} > 22 )); then
PKG_SDKMANAGER=google-android-cmdline-tools-13.0-installer
DIR_SDK=/usr/lib/android-sdk
else
PKG_SDKMANAGER=sdkmanager
DIR_SDK=/opt/android-sdk
fi
apt-get -q -y install gradle maven pandoc $PKG_SDKMANAGER jq openjdk-${JAVA_VERSION}-jdk
sdkmanager platform-tools
yes | sdkmanager --licenses
chown -R build:build $DIR_SDK
echo "export ANDROID_HOME=$DIR_SDK" >> /usr/bin/bashwrapper
echo "export JAVA_HOME_${JAVA_VERSION}=/usr/lib/jvm/java-${JAVA_VERSION}-openjdk-amd64" >> /usr/bin/bashwrapper
EOF
# Finish bashwrapper script and adjust permissions
RUN <<EOF cat >> /usr/bin/bashwrapper
if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then
/usr/bin/run-tests.sh "\${@:-bash}"
else
"\${@:-bash}"
fi
EOF
# now, switch to build user
USER build
VOLUME /home/build/build
WORKDIR /home/build/build
# Pre-install gradle. This will put files in ~/.gradle which will speed up builds.
# Note it would be safer to copy these files directly from our repo rather than
# getting it over the Internet, but Docker doesn't allow us to copy files
# from outside the current directory when building the image.
RUN mkdir -p $HOME/tmp/gradle/wrapper && \
# KMEA uses gradle-7.6.4-bin
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.jar && \
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradle/wrapper/gradle-wrapper.properties && \
curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/KMEA/gradlew && \
chmod +x $HOME/tmp/gradlew && \
$HOME/tmp/gradlew --quiet && \
# Some projects use gradle-7.6.4-all, so we pre-install that as well
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.jar https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.jar && \
curl --location --output $HOME/tmp/gradle/wrapper/gradle-wrapper.properties https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties && \
curl --location --output $HOME/tmp/gradlew https://raw.githubusercontent.com/keymanapp/keyman/master/android/Samples/KMSample1/gradlew && \
chmod +x $HOME/tmp/gradlew && \
$HOME/tmp/gradlew --quiet && \
rm -rf $HOME/tmp
ENTRYPOINT [ "/usr/bin/bashwrapper" ]

View file

@ -0,0 +1,59 @@
# Keyman is copyright (C) SIL Global. MIT License.
ARG UBUNTU_VERSION=latest
FROM ubuntu:${UBUNTU_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Build Base Image"
# We will switch to a build user after some installation
USER root
ENV HOME=/home/build
RUN grep ubuntu /etc/passwd && userdel ubuntu || true && \
rm -rf /home/ubuntu && \
useradd -c "Build user" --uid 1000 --home-dir $HOME --create-home --shell /usr/bin/bashwrapper build
ENV DEBIAN_FRONTEND=noninteractive
ENV DEBIAN_PRIORITY=critical
ENV DEBCONF_NOWARNINGS=yes
# Update to the latest
RUN apt-get -q -y update && \
apt-get -q -y install ca-certificates curl gnupg meson software-properties-common sudo && \
add-apt-repository ppa:keymanapp/keyman && \
add-apt-repository ppa:keymanapp/keyman-alpha
RUN apt-get -q -y update && \
apt-get -q -y upgrade
# Allow build user to use `sudo`
RUN echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN <<EOF cat > /usr/bin/bashwrapper
#!/bin/bash
export KEYMAN_USE_NVM=1
export DOCKER_RUNNING=true
EOF
# Install NVM
RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash
RUN <<EOF cat >> /usr/bin/bashwrapper
PATH=/home/build/.keyman/node:\$PATH
export NVM_DIR="$HOME/.nvm"
. /home/build/.nvm/nvm.sh
EOF
RUN chmod +x /usr/bin/bashwrapper && \
chown -R build:build $HOME
USER build
# Pre-install node
ARG REQUIRED_NODE_VERSION=unset
RUN echo "HOME=\${HOME}; REQUIRED_NODE_VERSION=${REQUIRED_NODE_VERSION}" && \
export NVM_DIR="/home/build/.nvm" && \
. /home/build/.nvm/nvm.sh && \
nvm install "${REQUIRED_NODE_VERSION}" && \
nvm use "${REQUIRED_NODE_VERSION}"
USER root

122
resources/docker-images/build.sh Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
################################ Main script ################################
. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
builder_describe \
"Build docker images" \
":android" \
":base" \
":core" \
":linux" \
":web" \
"--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \
"--no-cache Force rebuild of docker images" \
"build Build docker images" \
"test Test the docker images by running configure,build,test for all or the specified platforms"
builder_parse "$@"
_add_build_args() {
local var=$1
local default_var=$2
local name=$3
local value
if [[ -n "${!var:-}" ]]; then
value="${!var}"
else
value="${!default_var:-}"
fi
build_args+=(--build-arg="${var}=${value}")
if [[ -n "${build_version:-}" ]]; then
build_version="${build_version}-${name:-}${value}"
else
build_version="${name}${value}"
fi
}
_convert_parameters_to_build_args() {
build_args=()
build_version=
local required_node_version
# shellcheck disable=SC2034
required_node_version="$(_print_expected_node_version)"
_add_build_args UBUNTU_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER ""
_add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java
_add_build_args REQUIRED_NODE_VERSION required_node_version node
_add_build_args REQUIRED_EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emsdk
if [[ -n "${BASE_VERSION:-}" ]]; then
build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}")
fi
}
_is_default_values() {
[[ -z "${UBUNTU_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]]
}
build_action() {
local platform=$1
builder_echo debug "Building image for ${platform}"
_convert_parameters_to_build_args
if [[ "${platform}" == "base" ]]; then
docker pull --platform "amd64" "ubuntu:${UBUNTU_VERSION:-${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER}}"
elif [[ "${platform}" == "linux" ]]; then
cp "${KEYMAN_ROOT}/linux/debian/control" "${platform}"
fi
if builder_has_option --no-cache; then
OPTION_NO_CACHE="--no-cache"
fi
# shellcheck disable=SC2164
cd "${platform}"
# shellcheck disable=SC2248,SC2086
docker build ${OPTION_NO_CACHE:-} --platform amd64 -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" .
# If the user didn't specify particular versions we will additionaly create an image
# with the tag 'default'.
if _is_default_values; then
builder_echo debug "Setting default tag for ${platform}"
docker build --platform amd64 -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" .
fi
# shellcheck disable=SC2164,SC2103
cd -
builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built"
}
test_action() {
local platform=$1
builder_echo debug "Testing image for ${platform}"
./run.sh "${platform}" -- ./build.sh configure,build,test:"${platform}"
}
if builder_has_action build; then
build_action base
BASE_VERSION="${build_version}"
builder_run_action build:android build_action android
builder_run_action build:core build_action core
builder_run_action build:linux build_action linux
builder_run_action build:web build_action web
fi
builder_run_action test:core test_action core
builder_run_action test:linux test_action linux
builder_run_action test:web test_action web
# Android uses artifacts from web, so it has to come after web
builder_run_action test:android test_action android

View file

@ -0,0 +1,54 @@
# Keyman is copyright (C) SIL Global. MIT License.
#
# ARGS used in this file:
# - ARG BASE_VERSION
# - ARG REQUIRED_EMSCRIPTEN_VERSION
ARG BASE_VERSION=default
FROM keymanapp/keyman-base-ci:${BASE_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Core Build Image"
USER root
RUN apt-get install -qy git jq llvm meson pkgconf \
xvfb xserver-xephyr metacity
# Pre-install emscripten
USER build
ARG REQUIRED_EMSCRIPTEN_VERSION=unset
RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \
export EMSDK_KEEP_DOWNLOADS=1 && \
cd /home/build/ && \
git clone https://github.com/emscripten-core/emsdk.git && \
cd emsdk && \
./emsdk install ${REQUIRED_EMSCRIPTEN_VERSION} && \
./emsdk activate ${REQUIRED_EMSCRIPTEN_VERSION}
USER root
RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper && \
echo "export KEYMAN_USE_EMSDK=1" >> /usr/bin/bashwrapper
# Finish bashwrapper script and adjust permissions
RUN <<EOF cat >> /usr/bin/bashwrapper
if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then
/usr/bin/run-tests.sh "\${@:-bash}"
else
"\${@:-bash}"
fi
EOF
# now, switch to build user
USER build
# Pre-install node
RUN export NVM_DIR="/home/build/.nvm" && \
. /home/build/.nvm/nvm.sh && \
cd /home/build/emsdk/upstream/emscripten && \
npm install
VOLUME /home/build/build
WORKDIR /home/build/build
ENTRYPOINT [ "/usr/bin/bashwrapper" ]

View file

@ -0,0 +1 @@
control

View file

@ -0,0 +1,52 @@
# Keyman is copyright (C) SIL Global. MIT License.
ARG BASE_VERSION=default
FROM keymanapp/keyman-base-ci:${BASE_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Linux Build Image"
# Install dependencies
ADD control /tmp/control
# Answer 'yes' to install questions
RUN apt-get install -qy python3 python3-setuptools python3-coverage \
devscripts equivs libdatetime-perl lcov gcovr xvfb \
xserver-xephyr metacity mutter dbus-x11 weston xwayland && \
(yes | mk-build-deps --install /tmp/control) || true && \
rm /tmp/control
# Install lcov for code coverage
# Update to the latest and install packages needed for Linux coverage reporting
# and integration tests. We need at least version 2.0 of lcov. However,
# version 2.0-4 from Noble doesn't work either on Jammy. So we use
# version 2.0-1 from Mantic.
RUN LCOV_VERSION=$(dpkg -s lcov | grep Version | cut -d' ' -f2) && \
if dpkg --compare-versions "${LCOV_VERSION}" lt 2.0; then \
curl -sS -o /tmp/lcov.deb --location http://mirrors.kernel.org/ubuntu/pool/universe/l/lcov/lcov_2.0-1_all.deb && \
apt-get -qy install /tmp/lcov.deb && \
rm /tmp/lcov.deb ; \
fi
RUN mkdir -p /var/run/1000 && \
chown build:build /var/run/1000 && \
echo "export XDG_RUNTIME_DIR=/var/run/1000" >> /usr/bin/bashwrapper
COPY run-tests.sh /usr/bin/run-tests.sh
# Finish bashwrapper script and adjust permissions
RUN <<EOF cat >> /usr/bin/bashwrapper
if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then
/usr/bin/run-tests.sh "\${@:-bash}"
else
"\${@:-bash}"
fi
EOF
# now, switch to build user
USER build
VOLUME /home/build/build
WORKDIR /home/build/build
ENTRYPOINT [ "/usr/bin/bashwrapper" ]

View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e
if [[ -z "${DOCKER_RUNNING:-}" ]]; then
echo "This script is intended to be run inside a docker container."
exit 0
fi
# Start system dbus
sudo dbus-daemon --system --fork
# Start session dbus
# shellcheck disable=SC2046 # SC2046: quote this to prevent word-splitting
export $(dbus-launch)
# Start Wayland
weston --no-config --socket=wayland-0 --backend=headless &
export WAYLAND_DISPLAY=wayland-0
# Start X11 (on Wayland)
Xwayland &
export DISPLAY=:0
"${@:-bash}"

59
resources/docker-images/run.sh Executable file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh"
################################ Main script ################################
builder_describe \
"Run build.sh script inside of a docker image. Pass the build script and parameters after --." \
"android" \
"core" \
"linux" \
"web" \
"--ubuntu-version=UBUNTU_VERSION The Ubuntu version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})"
builder_parse "$@"
run_android() {
docker run -it --rm -v "${KEYMAN_ROOT}":/home/build/build \
-v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \
keymanapp/keyman-android-ci:default \
"${builder_extra_params[@]}"
}
run_core() {
docker run -it --rm -v "${KEYMAN_ROOT}":/home/build/build \
-v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \
keymanapp/keyman-core-ci:default \
"${builder_extra_params[@]}"
}
run_linux() {
mkdir -p "${KEYMAN_ROOT}/linux/build/docker-linux"
docker run -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \
-v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \
-v "${KEYMAN_ROOT}/linux/build/docker-linux":/home/build/build/linux/build \
-e DESTDIR=/tmp \
keymanapp/keyman-linux-ci:default \
"${builder_extra_params[@]}"
}
run_web() {
docker run -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \
-v "${KEYMAN_ROOT}/core/build/docker-core":/home/build/build/core/build \
keymanapp/keyman-web-ci:default \
"${builder_extra_params[@]}"
}
mkdir -p "${KEYMAN_ROOT}/core/build/docker-core"
builder_run_action android run_android
builder_run_action core run_core
builder_run_action linux run_linux
builder_run_action web run_web

View file

@ -0,0 +1,77 @@
# Keyman is copyright (C) SIL Global. MIT License.
#
# ARGS used in this file:
# - ARG BASE_VERSION=default
# - ARG REQUIRED_EMSCRIPTEN_VERSION=unset
ARG BASE_VERSION=default
FROM keymanapp/keyman-base-ci:${BASE_VERSION}
LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman for Web Build Image"
USER root
RUN apt-get install -qy git jq xvfb xserver-xephyr metacity
# For playwright:
RUN apt-get install -qy libevent-2.1-7t64 libxslt1.1 libwoff1 libvpx9 \
libgstreamer-plugins-bad1.0-0 libwebpdemux2 libharfbuzz-icu0 \
libenchant-2-2 libsecret-1-0 libhyphen0 libmanette-0.2-0 libflite1 \
gstreamer1.0-libav
COPY run-tests.sh /usr/bin/run-tests.sh
# Pre-install emscripten
USER build
ARG REQUIRED_EMSCRIPTEN_VERSION=unset
RUN echo "Installing emscripten version ${REQUIRED_EMSCRIPTEN_VERSION}" && \
export EMSDK_KEEP_DOWNLOADS=1 && \
cd /home/build/ && \
git clone https://github.com/emscripten-core/emsdk.git && \
cd emsdk && \
./emsdk install ${REQUIRED_EMSCRIPTEN_VERSION} && \
./emsdk activate ${REQUIRED_EMSCRIPTEN_VERSION}
USER root
RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/bin/bashwrapper && \
echo "export KEYMAN_USE_EMSDK=1" >> /usr/bin/bashwrapper
# Keyman Web
RUN curl --output google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get -qy install ./google-chrome-stable_current_amd64.deb && \
rm google-chrome-stable_current_amd64.deb && \
echo "export CHROME_BIN=/opt/google/chrome/chrome" >> /usr/bin/bashwrapper
RUN <<EOF cat > /etc/apt/preferences.d/mozilla
Package: *
Pin: origin packages.mozilla.org
Pin-Priority: 1000
EOF
RUN curl https://packages.mozilla.org/apt/repo-signing-key.gpg > /etc/apt/keyrings/packages.mozilla.org.asc && \
echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" >> /etc/apt/sources.list.d/mozilla.list && \
apt-get update && \
apt-get -qy install firefox && \
echo "export FIREFOX_BIN=/usr/bin/firefox" >> /usr/bin/bashwrapper
# Finish bashwrapper script and adjust permissions
RUN <<EOF cat >> /usr/bin/bashwrapper
if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then
/usr/bin/run-tests.sh "\${@:-bash}"
else
"\${@:-bash}"
fi
EOF
# now, switch to build user
USER build
# Pre-install node
RUN export NVM_DIR="/home/build/.nvm" && \
. /home/build/.nvm/nvm.sh && \
cd /home/build/emsdk/upstream/emscripten && \
npm install
VOLUME /home/build/build
WORKDIR /home/build/build
ENTRYPOINT [ "/usr/bin/bashwrapper" ]

View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
if [[ -z "${DOCKER_RUNNING:-}" ]]; then
echo "This script is intended to be run inside a docker container."
exit 0
fi
set -e
echo "Starting Xvfb..."
Xvfb -screen 0 1024x768x24 :33 &> /dev/null &
sleep 1
echo "Starting Xephyr..."
DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null &
sleep 1
echo "Starting metacity"
metacity --display=:32 &> /dev/null &
export DISPLAY=:32
"${@:-bash}"

View file

@ -277,10 +277,14 @@ verify_npm_setup() {
popd > /dev/null
}
_print_expected_node_version() {
"$JQ" -r '.engines.node' "$KEYMAN_ROOT/package.json"
}
# Use nvm to select a node version according to package.json
# see /docs/build/node.md
_select_node_version_with_nvm() {
local REQUIRED_NODE_VERSION="$("$JQ" -r '.engines.node' "$KEYMAN_ROOT/package.json")"
local REQUIRED_NODE_VERSION="$(_print_expected_node_version)"
local CURRENT_NODE_VERSION
if [[ $BUILDER_OS != win ]]; then

View file

@ -166,7 +166,10 @@ builder_run_child_actions build:engine/attachment
# Uses engine/interfaces (due to resource-path config interface)
builder_run_child_actions build:engine/keyboard-storage
# Uses engine/interfaces, engine/keyboard-storage, & engine/osk
# Builds the predictive-text components
builder_run_child_actions build:engine/predictive-text
# Uses engine/interfaces, engine/keyboard-storage, engine/predictive-text, & engine/osk
builder_run_child_actions build:engine/main
# Uses all but engine/element-wrappers and engine/attachment
@ -190,7 +193,7 @@ builder_run_child_actions build:test-pages
builder_run_action build:_all build_action
# Run tests
# builder_run_child_actions test
builder_run_child_actions test
builder_run_action test:_all test_action
function do_test_help() {

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
#
# Compile keymanweb predictive-text components.
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
# ################################ Main script ################################
builder_describe "Builds predictive-text components used within Keyman Engine for Web (KMW)." \
"clean" \
"configure" \
"build" \
"test" \
":templates Builds the model templates utilized by compiled lexical models" \
":wordbreakers Builds the wordbreakers provided for lexical model use" \
":worker-main Builds the predictive-text worker interface module" \
":worker-thread Builds the predictive-text worker" \
":_all (Meta build target used when targets are not specified)" \
"--ci+ Set to utilize CI-based test configurations & reporting."
# Possible TODO?
# "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \
builder_parse "$@"
config=release
if builder_is_debug_build; then
config=debug
fi
builder_describe_outputs \
configure "/node_modules" \
build:templates "/web/src/engine/predictive-text/build/obj/index.js" \
build:wordbreakers "/web/src/engine/wordbreakers/build/main/obj/index.js" \
build:worker-main "/web/src/engine/worker-main/build/obj/lmlayer.js" \
build:worker-thread "/web/src/engine/worker-thread/build/obj/worker-main.wrapped.js"
BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mjs"
#### Build action definitions ####
# We can run all clean & configure actions at once without much issue.
builder_run_child_actions clean
builder_run_child_actions configure
## Build actions
builder_run_child_actions build:wordbreakers
builder_run_child_actions build:templates
builder_run_child_actions build:worker-thread
builder_run_child_actions build:worker-main
# If doing CI testing, the predictive-text child actions have their own build configuration.
# For local testing, though, we can allow them to proceed.
if ! builder_has_option --ci; then
builder_run_child_actions test
fi

View file

@ -57,12 +57,13 @@ function do_build() {
function do_test() {
local TEST_OPTIONS=
if builder_has_option --ci; then
TEST_OPTIONS=--ci
# We'll test the included libraries here for now. At some point, we may wish
# to establish a ci.sh script for predictive-text to handle this instead.
./unit_tests/test.sh test:libraries test:headless test:browser --ci
else
# If we're not in --ci mode, then this doesn't need to trigger the sibling projects' tests.
./unit_tests/test.sh test:headless test:browser
fi
# We'll test the included libraries here for now. At some point, we may wish
# to establish a ci.sh script for predictive-text to handle this instead.
./unit_tests/test.sh test:libraries test:headless test:browser $TEST_OPTIONS
}
builder_run_action configure do_configure

View file

@ -43,7 +43,6 @@ uses
InterfaceHotkeys in '..\..\global\delphi\general\InterfaceHotkeys.pas',
utilsystem in '..\..\..\..\common\windows\delphi\general\utilsystem.pas',
Upload_Settings in '..\..\..\..\common\windows\delphi\general\Upload_Settings.pas',
UfrmOnlineUpdateNewVersion in 'main\UfrmOnlineUpdateNewVersion.pas' {frmOnlineUpdateNewVersion},
OnlineUpdateCheck in 'main\OnlineUpdateCheck.pas',
utilxml in '..\..\..\..\common\windows\delphi\general\utilxml.pas',
UfrmInstallKeyboardFromWeb in 'install\UfrmInstallKeyboardFromWeb.pas' {frmInstallKeyboardFromWeb},
@ -77,7 +76,6 @@ uses
UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas',
UILanguages in 'util\UILanguages.pas',
UfrmKeyboardOptions in 'main\UfrmKeyboardOptions.pas' {frmKeyboardOptions},
UfrmOnlineUpdateIcon in 'main\UfrmOnlineUpdateIcon.pas' {frmOnlineUpdateIcon},
KeymanTrayIcon in '..\..\engine\keyman\KeymanTrayIcon.pas',
UImportOlderVersionKeyboards10 in 'main\UImportOlderVersionKeyboards10.pas',
VisualKeyboard in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas',
@ -177,7 +175,15 @@ uses
Keyman.Configuration.System.HttpServer.App.TextEditorFonts in 'startup\help\Keyman.Configuration.System.HttpServer.App.TextEditorFonts.pas',
Keyman.Configuration.System.HttpServer.App.Locale in 'web\Keyman.Configuration.System.HttpServer.App.Locale.pas',
Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas',
Keyman.Configuration.System.Main in 'main\Keyman.Configuration.System.Main.pas';
Keyman.Configuration.System.Main in 'main\Keyman.Configuration.System.Main.pas',
UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas',
Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas',
Keyman.System.RemoteUpdateCheck in 'main\Keyman.System.RemoteUpdateCheck.pas',
Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas',
Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas',
Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas',
Keyman.Configuration.UI.UfrmStartInstallNow in 'main\Keyman.Configuration.UI.UfrmStartInstallNow.pas' {frmInstallNow},
Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall};
{$R VERSION.RES}
{$R manifest.res}

View file

@ -171,9 +171,6 @@
<DCCReference Include="..\..\global\delphi\general\InterfaceHotkeys.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\utilsystem.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Upload_Settings.pas"/>
<DCCReference Include="main\UfrmOnlineUpdateNewVersion.pas">
<Form>frmOnlineUpdateNewVersion</Form>
</DCCReference>
<DCCReference Include="main\OnlineUpdateCheck.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\utilxml.pas"/>
<DCCReference Include="install\UfrmInstallKeyboardFromWeb.pas">
@ -224,9 +221,6 @@
<DCCReference Include="main\UfrmKeyboardOptions.pas">
<Form>frmKeyboardOptions</Form>
</DCCReference>
<DCCReference Include="main\UfrmOnlineUpdateIcon.pas">
<Form>frmOnlineUpdateIcon</Form>
</DCCReference>
<DCCReference Include="..\..\engine\keyman\KeymanTrayIcon.pas"/>
<DCCReference Include="main\UImportOlderVersionKeyboards10.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas"/>
@ -354,6 +348,20 @@
<DCCReference Include="web\Keyman.Configuration.System.HttpServer.App.Locale.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas"/>
<DCCReference Include="main\Keyman.Configuration.System.Main.pas"/>
<DCCReference Include="render\UpdateXMLRenderer.pas"/>
<DCCReference Include="main\Keyman.System.UpdateCheckStorage.pas"/>
<DCCReference Include="main\Keyman.System.RemoteUpdateCheck.pas"/>
<DCCReference Include="main\Keyman.System.UpdateStateMachine.pas"/>
<DCCReference Include="main\Keyman.System.DownloadUpdate.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas"/>
<DCCReference Include="main\Keyman.Configuration.UI.UfrmStartInstallNow.pas">
<Form>frmInstallNow</Form>
<FormType>dfm</FormType>
</DCCReference>
<DCCReference Include="main\Keyman.Configuration.UI.UfrmStartInstall.pas">
<Form>frmStartInstall</Form>
<FormType>dfm</FormType>
</DCCReference>
<None Include="Profiling\AQtimeModule1.aqt"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
@ -415,13 +423,19 @@
<Platform value="Win64">False</Platform>
</Platforms>
<Deployment Version="3">
<DeployFile LocalName="kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<DeployFile LocalName="bin\Win32\Debug\kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win32">
<RemoteName>kmshell.rsm</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>kmshell.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>kmshell.exe</RemoteName>
<Overwrite>true</Overwrite>
@ -1228,6 +1242,7 @@
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
</Deployment>
<ModelSupport>False</ModelSupport>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>

View file

@ -0,0 +1,10 @@
``` mermaid
stateDiagram
[*] --> Idle
Idle --> UpdateAvailable
UpdateAvailable --> Downloading
Downloading --> Installing
Downloading --> WaitingRestart
WaitingRestart --> Installing
Installing --> Idle
```

View file

@ -0,0 +1,50 @@
object frmStartInstall: TfrmStartInstall
Left = 0
Top = 0
BorderIcons = [biSystemMenu]
BorderStyle = bsDialog
Caption = 'Keyman Update'
ClientHeight = 142
ClientWidth = 322
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
PixelsPerInch = 96
TextHeight = 13
object lblInstallUpdate: TLabel
Left = 72
Top = 48
Width = 180
Height = 19
Caption = 'Keyman update available.'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
end
object cmdInstall: TButton
Left = 140
Top = 104
Width = 75
Height = 25
Caption = 'Install'
ModalResult = 1
TabOrder = 0
end
object cmdLater: TButton
Left = 234
Top = 104
Width = 75
Height = 25
Caption = 'Close'
ModalResult = 8
TabOrder = 1
end
end

View file

@ -0,0 +1,39 @@
{
Keyman is copyright (C) SIL Global. MIT License.
// TODO: #12887 Localise all the labels and captions.
}
unit Keyman.Configuration.UI.UfrmStartInstall;
interface
uses
System.Classes,
System.SysUtils,
System.Variants,
Vcl.Controls,
Vcl.Dialogs,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.Graphics,
Vcl.StdCtrls,
Winapi.Messages,
Winapi.Windows,
UfrmKeymanBase,
UserMessages;
type
TfrmStartInstall = class(TfrmKeymanBase)
cmdInstall: TButton;
cmdLater: TButton;
lblInstallUpdate: TLabel;
private
public
end;
implementation
{$R *.dfm}
end.

View file

@ -0,0 +1,51 @@
object frmStartInstallNow: TfrmStartInstallNow
Left = 0
Top = 0
BorderIcons = [biSystemMenu]
BorderStyle = bsDialog
Caption = 'Keyman Update'
ClientHeight = 164
ClientWidth = 354
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
PixelsPerInch = 96
TextHeight = 13
object lblUpdateMessage: TLabel
Left = 32
Top = 48
Width = 290
Height = 41
Caption = 'Your computer will be restarted if you update now.'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
WordWrap = True
end
object cmdInstall: TButton
Left = 147
Top = 120
Width = 75
Height = 25
Caption = 'Update now'
ModalResult = 1
TabOrder = 0
end
object cmdLater: TButton
Left = 247
Top = 120
Width = 75
Height = 25
Caption = 'Close'
ModalResult = 8
TabOrder = 1
end
end

View file

@ -0,0 +1,39 @@
{
Keyman is copyright (C) SIL Global. MIT License.
// TODO: #12887 Localise all the labels and captions.
}
unit Keyman.Configuration.UI.UfrmStartInstallNow;
interface
uses
System.Classes,
System.SysUtils,
System.Variants,
Vcl.Controls,
Vcl.Dialogs,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.Graphics,
Vcl.StdCtrls,
Winapi.Messages,
Winapi.Windows,
UfrmKeymanBase,
UserMessages;
type
TfrmStartInstallNow = class(TfrmKeymanBase)
cmdInstall: TButton;
cmdLater: TButton;
lblUpdateMessage: TLabel;
private
public
end;
implementation
{$R *.dfm}
end.

View file

@ -0,0 +1,186 @@
(*
* Keyman is copyright (C) SIL Global. MIT License.
*)
unit Keyman.System.DownloadUpdate;
interface
uses
System.Classes,
System.SysUtils,
Sentry.Client,
httpuploader,
Keyman.System.KeymanSentryClient,
Keyman.System.UpdateCheckResponse,
KeymanPaths;
type
TDownloadUpdateParams = record
TotalSize: Integer;
TotalDownloads: Integer;
StartPosition: Integer;
end;
TDownloadUpdate = class
private
FShowErrors: Boolean;
FDownload: TDownloadUpdateParams;
(**
*
* Performs updates download in the background.
* @params SavePath The path where the downloaded files will be saved.
*
*@returns A Boolean value indicating the overall result of the
* download process.
*)
function DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse): Boolean;
public
constructor Create;
destructor Destroy; override;
function DownloadUpdates : Boolean;
// TODO: #12888 verify filesizes match the ucr metadata so we know we don't have partial downloads.
//function VerifyAllFilesDownloaded : Boolean;
property ShowErrors: Boolean read FShowErrors write FShowErrors;
end;
implementation
uses
System.StrUtils,
System.Types,
ErrorControlledRegistry,
GlobalProxySettings,
keymanapi_TLB,
KeymanVersion,
Keyman.System.UpdateCheckStorage,
KLog,
kmint,
OnlineUpdateCheckMessages,
RegistryKeys,
Upload_Settings,
utilkmshell;
procedure ErrorLogMessage(ErrorLogMessage: string);
begin
KL.Log(ErrorLogMessage);
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
ErrorLogMessage);
end;
constructor TDownloadUpdate.Create;
begin
inherited Create;
FShowErrors := True;
KL.Log('TDownloadUpdate.Create');
end;
destructor TDownloadUpdate.Destroy;
begin
inherited Destroy;
end;
function TDownloadUpdate.DoDownloadUpdates(SavePath: string; Params: TUpdateCheckResponse): Boolean;
var
i : Integer;
http: THttpUploader;
fs: TFileStream;
function DownloadFile(const url, savepath: string): Boolean;
begin
try
http := THttpUploader.Create(nil);
try
http.Proxy.Server := GetProxySettings.Server;
http.Proxy.Port := GetProxySettings.Port;
http.Proxy.Username := GetProxySettings.Username;
http.Proxy.Password := GetProxySettings.Password;
http.Request.Agent := API_UserAgent;
http.Request.SetURL(url);
http.Upload;
if http.Response.StatusCode = 200 then
begin
fs := TFileStream.Create(savepath, fmCreate);
try
fs.Write(http.Response.PMessageBody^, http.Response.MessageBodyLength);
finally
fs.Free;
end;
Result := True;
end
else // I2742
begin
// If it fails we set to false but will try the other files
Exit(False);
end;
finally
http.Free;
end;
except
on E:EHTTPUploader do
begin
if (E.ErrorCode = 12007) or (E.ErrorCode = 12029)
then ErrorLogMessage(S_OnlineUpdate_UnableToContact)
else ErrorLogMessage(WideFormat(S_OnlineUpdate_UnableToContact_Error, [E.Message]));
Result := False;
end;
end;
end;
begin
Result := False;
FDownload.TotalSize := 0;
FDownload.TotalDownloads := 0;
// Keyboard Packages
FDownload.StartPosition := 0;
for i := 0 to High(Params.Packages) do
begin
Inc(FDownload.TotalDownloads);
Inc(FDownload.TotalSize, Params.Packages[i].DownloadSize);
Params.Packages[i].SavePath := SavePath + Params.Packages[i].FileName;
if not DownloadFile(Params.Packages[i].DownloadURL, Params.Packages[i].SavePath) then // I2742
begin
Params.Packages[i].Install := False; // Download failed but install other files
end;
FDownload.StartPosition := FDownload.StartPosition + Params.Packages[i].DownloadSize;
end;
// Keyman Installer
Inc(FDownload.TotalDownloads);
Inc(FDownload.TotalSize, Params.InstallSize);
if not DownloadFile(Params.InstallURL, SavePath + Params.FileName) then // I2742
begin
ErrorLogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL);
end
else
begin
// If installer has downloaded that is success even
// if zero packages were downloaded.
Result := True;
end;
end;
function TDownloadUpdate.DownloadUpdates: Boolean;
var
DownloadBackGroundSavePath : String;
ucr: TUpdateCheckResponse;
begin
DownloadBackGroundSavePath := IncludeTrailingPathDelimiter(TKeymanPaths.KeymanUpdateCachePath);
if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then
begin
Result := DoDownloadUpdates(DownloadBackGroundSavePath, ucr);
KL.Log('DownloadUpdates.DownloadUpdates: DownloadResult = '+IntToStr(Ord(Result)));
end
else
Result := False;
end;
end.

View file

@ -0,0 +1,262 @@
(**
* Keyman is copyright (C) SIL International. MIT License.
*
* Keyman.System.RemoteUpdateCheck: Checks for keyboard package and Keyman
* for Windows updates.
*)
unit Keyman.System.RemoteUpdateCheck; // I3306
interface
uses
System.Classes,
System.SysUtils,
KeymanPaths,
httpuploader,
Keyman.System.UpdateCheckResponse;
const
CheckPeriod: Integer = 7; // Days between checking for updates
type
ERemoteUpdateCheck = class(Exception);
TRemoteUpdateCheckResult = (wucUnknown, wucSuccess, wucNoUpdates, wucFailure, wucOffline);
TRemoteUpdateCheckDownloadParams = record
TotalSize: Integer;
TotalDownloads: Integer;
StartPosition: Integer;
end;
TRemoteUpdateCheck = class
private
FForce: Boolean;
FRemoteResult: TRemoteUpdateCheckResult;
FErrorMessage: string;
FShowErrors: Boolean;
(**
* Performs an online query of both the main keyman package and
* the keyboard packages. It utilizes the kmcom API to retrieve the current
* packages. The function then performs an HTTP request to query the remote
* versions of these packages.
* The resulting information is stored in the TUpdateCheckResponse
* variable and seralized to disk.
*
* @returns A TBackgroundUpdateResult indicating the result of the update
* check.
*)
function DoRun: TRemoteUpdateCheckResult;
public
constructor Create(AForce: Boolean);
destructor Destroy; override;
function Run: TRemoteUpdateCheckResult;
property ShowErrors: Boolean read FShowErrors write FShowErrors;
end;
(**
* This function checks if a week or CheckPeriod time has passed since the last
* update check.
*
* @returns True if it has been longer then CheckPeriod time since last update
*)
function ConfigCheckContinue: Boolean;
implementation
uses
System.WideStrUtils,
System.Win.Registry,
Winapi.Windows,
Winapi.WinINet,
Sentry.Client,
GlobalProxySettings,
KLog,
keymanapi_TLB,
KeymanVersion,
Keyman.System.KeymanSentryClient,
Keyman.System.UpdateCheckStorage,
kmint,
ErrorControlledRegistry,
RegistryKeys,
Upload_Settings,
OnlineUpdateCheckMessages;
{ TRemoteUpdateCheck }
constructor TRemoteUpdateCheck.Create(AForce: Boolean);
begin
inherited Create;
FShowErrors := True;
FRemoteResult := wucUnknown;
FForce := AForce;
KL.Log('TRemoteUpdateCheck.Create');
end;
destructor TRemoteUpdateCheck.Destroy;
begin
if (FErrorMessage <> '') and FShowErrors then
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
'"+FErrorMessage+"');
KL.Log('TRemoteUpdateCheck.Destroy: FErrorMessage = ' + FErrorMessage);
KL.Log('TRemoteUpdateCheck.Destroy: FRemoteResult = ' +
IntToStr(Ord(FRemoteResult)));
inherited Destroy;
end;
function TRemoteUpdateCheck.Run: TRemoteUpdateCheckResult;
begin
Result := DoRun;
FRemoteResult := Result;
end;
function TRemoteUpdateCheck.DoRun: TRemoteUpdateCheckResult;
var
flags: DWord;
i: Integer;
ucr: TUpdateCheckResponse;
pkg: IKeymanPackage;
Registry: TRegistryErrorControlled;
http: THttpUploader;
proceed: Boolean;
begin
{ FProxyHost := '';
FProxyPort := 0; }
{ Check if user is currently online }
if not InternetGetConnectedState(@flags, 0) then
begin
Result := wucOffline;
Exit;
end;
proceed := ConfigCheckContinue;
if not proceed and not FForce then
begin
Result := wucNoUpdates;
Exit;
end;
try
http := THttpUploader.Create(nil);
try
http.Fields.Add('version', ansistring(CKeymanVersionInfo.Version));
http.Fields.Add('tier', ansistring(CKeymanVersionInfo.Tier));
if FForce then
http.Fields.Add('manual', '1')
else
http.Fields.Add('manual', '0');
for i := 0 to kmcom.Packages.Count - 1 do
begin
pkg := kmcom.Packages[i];
// Due to limitations in PHP parsing of query string parameters names with
// space or period, we need to split the parameters up. The legacy pattern
// is still supported on the server side. Relates to #4886.
http.Fields.Add(ansistring('packageid_' + IntToStr(i)),
ansistring(pkg.ID));
http.Fields.Add(ansistring('packageversion_' + IntToStr(i)),
ansistring(pkg.Version));
pkg := nil;
end;
http.Proxy.Server := GetProxySettings.Server;
http.Proxy.Port := GetProxySettings.Port;
http.Proxy.Username := GetProxySettings.Username;
http.Proxy.Password := GetProxySettings.Password;
http.Request.HostName := API_Server;
http.Request.Protocol := API_Protocol;
http.Request.UrlPath := API_Path_UpdateCheck_Windows;
// OnStatus :=
http.Upload;
if http.Response.StatusCode = 200 then
begin
if ucr.Parse(http.Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then
begin
TUpdateCheckStorage.SaveUpdateCacheData(ucr);
Result := wucSuccess;
end
else
begin
FErrorMessage := ucr.ErrorMessage;
Result := wucFailure;
end;
end
else
raise ERemoteUpdateCheck.Create('Error '+IntToStr(http.Response.StatusCode));
finally
http.Free;
end;
except
on E: EHTTPUploader do
begin
if (E.ErrorCode = 12007) or (E.ErrorCode = 12029) then
FErrorMessage := S_OnlineUpdate_UnableToContact
else
FErrorMessage := WideFormat(S_OnlineUpdate_UnableToContact_Error,
[E.Message]);
Result := wucFailure;
end;
on E: Exception do
begin
FErrorMessage := E.Message;
Result := wucFailure;
end;
end;
Registry := TRegistryErrorControlled.Create; // I2890
try
if Registry.OpenKey(SRegKey_KeymanDesktop_CU, True) then
Registry.WriteDateTime(SRegValue_LastUpdateCheckTime, Now);
finally
Registry.Free;
end;
end;
function ConfigCheckContinue: Boolean;
var
Registry: TRegistryErrorControlled;
begin
{ Verify that it has been at least CheckPeriod days since last update check }
Result := False;
try
Registry := TRegistryErrorControlled.Create; // I2890
try
if Registry.OpenKeyReadOnly(SRegKey_KeymanDesktop_CU) then
begin
if Registry.ValueExists(SRegValue_CheckForUpdates) and
not Registry.ReadBool(SRegValue_CheckForUpdates) then
begin
Exit;
end;
if Registry.ValueExists(SRegValue_LastUpdateCheckTime) and
(Now - Registry.ReadDateTime(SRegValue_LastUpdateCheckTime) >
CheckPeriod) then
begin
Result := True;
end;
end;
finally
Registry.Free;
end;
except
on E: ERegistryException do
begin
Result := False;
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
E.Message);
end;
end;
end;
end.

View file

@ -0,0 +1,82 @@
unit Keyman.System.UpdateCheckStorage;
interface
uses
Keyman.System.UpdateCheckResponse;
type
TUpdateCheckStorage = class sealed
private
class function MetadataFilename: string; static;
public
class function HasUpdates: Boolean; static;
class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static;
class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static;
class function HasKeyboardPackages(const data: TUpdateCheckResponse): Boolean; static;
class function HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean; static;
end;
implementation
uses
System.SysUtils,
System.RegularExpressions,
KeymanPaths,
KeymanVersion;
{ TUpdateCheckStorage }
class function TUpdateCheckStorage.MetadataFilename: string;
begin
Result := TKeymanPaths.KeymanUpdateCachePath(TKeymanPaths.S_UpdateCache_Metadata);
end;
class procedure TUpdateCheckStorage.SaveUpdateCacheData(
const data: TUpdateCheckResponse);
begin
ForceDirectories(TKeymanPaths.KeymanUpdateCachePath);
data.SaveToFile(MetadataFilename);
end;
class function TUpdateCheckStorage.HasUpdates: Boolean;
begin
Result := FileExists(MetadataFilename);
end;
class function TUpdateCheckStorage.LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean;
begin
Result :=
HasUpdates and
data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version);
end;
class function TUpdateCheckStorage.HasKeyboardPackages(const data: TUpdateCheckResponse): Boolean;
var
i : Integer;
fileName : string;
KeyboardRegex: TRegEx;
begin
Result := False;
KeyboardRegex := TRegEx.Create('\.k..$');
for i := 0 to High(data.Packages) do
begin
fileName := data.Packages[i].FileName;
if KeyboardRegex.IsMatch(fileName) then
Result := True;
end;
end;
class function TUpdateCheckStorage.HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean;
var
fileExtension: string;
begin
fileExtension := LowerCase(ExtractFileExt(data.FileName));
if fileExtension = '.exe' then
Result := True
else
Result := False
end;
end.

File diff suppressed because it is too large Load diff

View file

@ -98,11 +98,11 @@ type
FShowErrors: Boolean;
FDownload: TOnlineUpdateCheckDownloadParams;
FCheckOnly: Boolean;
function DownloadUpdates: Boolean;
procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean);
function DoRun: TOnlineUpdateCheckResult;
procedure ShowUpdateForm;
procedure ShutDown;
procedure DownloadUpdatesHTTPStatus(Sender: THTTPUploader;
const Message: string; Position, Total: Int64); // I2855
@ -112,7 +112,9 @@ type
public
public
constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean);
function ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams;
constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean = False);
destructor Destroy; override;
function Run: TOnlineUpdateCheckResult;
property ShowErrors: Boolean read FShowErrors write FShowErrors;
@ -146,6 +148,7 @@ uses
KLog,
keymanapi_TLB,
KeymanVersion,
Keyman.System.UpdateCheckStorage,
kmint,
ErrorControlledRegistry,
RegistryKeys,
@ -153,8 +156,6 @@ uses
utildir,
utilexecute,
OnlineUpdateCheckMessages,
UfrmOnlineUpdateIcon,
UfrmOnlineUpdateNewVersion,
utilkmshell,
utilsystem,
utiluac,
@ -165,7 +166,7 @@ const
{ TOnlineUpdateCheck }
constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean);
constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean);
begin
inherited Create;
@ -176,6 +177,7 @@ begin
FSilent := ASilent;
FForce := AForce;
FCheckOnly := ACheckOnly;
KL.Log('TOnlineUpdateCheck.Create');
end;
@ -388,86 +390,6 @@ begin
end;
end;
procedure TOnlineUpdateCheck.ShowUpdateForm;
var
i: Integer;
FRequiresAdmin: Boolean;
FOwnerHandle: THandle;
begin
if Assigned(FOwner)
then FOwnerHandle := FOwner.Handle
else FOwnerHandle := Application.Handle;
{ We have an update available }
with OnlineUpdateNewVersion(FOwner) do
try
Params := Self.FParams;
if ShowModal <> mrYes then
begin
Self.FParams.Result := oucUnknown;
Self.FErrorMessage := '';
Exit;
end;
Self.FParams := Params;
finally
Free;
end;
if not DownloadUpdates then
begin
Self.FParams.Result := oucUnknown; // I2742
Exit;
end
else
begin
if not kmcom.SystemInfo.IsAdministrator then
begin
FRequiresAdmin := FParams.Keyman.Install;
for i := 0 to High(FParams.Packages) do
if FParams.Packages[i].Install then
begin
FRequiresAdmin := True;
Break;
end;
end
else
FRequiresAdmin := False;
if FRequiresAdmin then
begin
if CanElevate then
begin
SavePackageUpgradesToDownloadTempPath;
if WaitForElevatedConfiguration(FOwnerHandle, '-ou "'+DownloadTempPath+'"', not FParams.Keyman.Install) <> 0 then // I2513
FParams.Result := oucFailure
else if FParams.Keyman.Install then
FParams.Result := oucShutDown
else
FParams.Result := oucSuccess;
end
else
begin
ShowMessage('Some of these updates require an Administrator to complete installation. Please login as an Administrator and re-run the update.');
FParams.Result := oucFailure;
end;
end
else
begin
FParams.Result := oucSuccess;
for i := 0 to High(FParams.Packages) do
if FParams.Packages[i].Install then
if not DoInstallPackage(FParams.Packages[i]) then FParams.Result := oucFailure;
if FParams.Keyman.Install then
begin
DoInstallKeyman;
FParams.Result := oucShutDown;
end;
end;
end;
end;
procedure TOnlineUpdateCheck.ShutDown;
begin
if Assigned(Application) then
@ -477,10 +399,9 @@ end;
function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult;
var
flags: DWord;
i, n: Integer;
pkg: IKeymanPackage;
j: Integer;
i: Integer;
ucr: TUpdateCheckResponse;
pkg: IKeymanPackage;
begin
{FProxyHost := '';
FProxyPort := 0;}
@ -567,54 +488,9 @@ begin
begin
if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then
begin
SetLength(FParams.Packages,0);
for i := Low(ucr.Packages) to High(ucr.Packages) do
begin
n := kmcom.Packages.IndexOf(ucr.Packages[i].ID);
if n >= 0 then
begin
pkg := kmcom.Packages[n];
j := Length(FParams.Packages);
SetLength(FParams.Packages, j+1);
FParams.Packages[j].NewID := ucr.Packages[i].NewID;
FParams.Packages[j].ID := ucr.Packages[i].ID;
FParams.Packages[j].Description := ucr.Packages[i].Name;
FParams.Packages[j].OldVersion := pkg.Version;
FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion;
FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize;
FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL;
FParams.Packages[j].FileName := ucr.Packages[i].FileName;
pkg := nil;
end
else
FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID;
end;
case ucr.Status of
ucrsNoUpdate:
begin
FErrorMessage := ucr.ErrorMessage;
end;
ucrsUpdateReady:
begin
FParams.Keyman.OldVersion := ucr.CurrentVersion;
FParams.Keyman.NewVersion := ucr.NewVersion;
FParams.Keyman.DownloadURL := ucr.InstallURL;
FParams.Keyman.DownloadSize := ucr.InstallSize;
FParams.Keyman.FileName := ucr.FileName;
end;
end;
if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then
begin
if not FSilent then
ShowUpdateForm
else
begin
ShowUpdateIcon;
end;
Result := FParams.Result;
end;
ResponseToParams(ucr);
TUpdateCheckStorage.SaveUpdateCacheData(ucr);
Result := FParams.Result;
end
else
begin
@ -651,6 +527,52 @@ begin
end;
end;
function TOnlineUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams;
var
i, j, n: Integer;
pkg: IKeymanPackage;
begin
SetLength(FParams.Packages,0);
for i := Low(ucr.Packages) to High(ucr.Packages) do
begin
n := kmcom.Packages.IndexOf(ucr.Packages[i].ID);
if n >= 0 then
begin
pkg := kmcom.Packages[n];
j := Length(FParams.Packages);
SetLength(FParams.Packages, j+1);
FParams.Packages[j].NewID := ucr.Packages[i].NewID;
FParams.Packages[j].ID := ucr.Packages[i].ID;
FParams.Packages[j].Description := ucr.Packages[i].Name;
FParams.Packages[j].OldVersion := pkg.Version;
FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion;
FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize;
FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL;
FParams.Packages[j].FileName := ucr.Packages[i].FileName;
pkg := nil;
end
else
FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID;
end;
case ucr.Status of
ucrsNoUpdate:
begin
FErrorMessage := ucr.ErrorMessage;
end;
ucrsUpdateReady:
begin
FParams.Keyman.OldVersion := ucr.CurrentVersion;
FParams.Keyman.NewVersion := ucr.NewVersion;
FParams.Keyman.DownloadURL := ucr.InstallURL;
FParams.Keyman.DownloadSize := ucr.InstallSize;
FParams.Keyman.FileName := ucr.FileName;
end;
end;
Result := FParams;
end;
procedure OnlineUpdateAdmin(OwnerForm: TCustomForm; Path: string);
var
Package: TOnlineUpdateCheckParamsPackage;

View file

@ -1,18 +1,18 @@
(*
Name: UImportOlderVersionSettings
Copyright: Copyright (C) 2003-2017 SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 22 Feb 2011
Modified Date: 3 Jun 2014
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 22 Feb 2011 - mcdurdin - I2651 - Install does not set desired default options
22 Feb 2011 - mcdurdin - I2753 - Firstrun crashes because start with windows and auto update check options are set in Engine instead of Desktop
03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors
@ -24,7 +24,7 @@ unit UImportOlderVersionSettings;
interface
function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753
function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates,DoAutomaticUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753
implementation
@ -41,14 +41,24 @@ uses
keymanapi_TLB,
ErrorControlledRegistry,
RegistryKeys,
Keyman.System.UpdateStateMachine,
UImportOlderKeyboardUtils;
function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753
function FirstRunInstallDefaults(DoDefaults,DoStartWithWindows,DoCheckForUpdates,DoAutomaticUpdates: Boolean; FDisablePackages, FDefaultUILanguage: string; DoAutomaticallyReportUsage: Boolean): Boolean; // I2753
var
n, I: Integer;
v: Integer;
p: string;
UpdateSM : TUpdateStateMachine;
begin
// send event to statemachine (should result in setting state to idle)
UpdateSM := TUpdateStateMachine.Create(False);
try
UpdateSM.HandleFirstRun;
finally
UpdateSM.Free;
end;
{ Copy over all the user settings and set defaults for version 8.0: http://blog.tavultesoft.com/2011/02/keyman-desktop-80-default-options.html }
if DoDefaults then // I2753
@ -136,6 +146,7 @@ begin
if DoStartWithWindows then kmcom.Options['koStartWithWindows'].Value := True; // I2753
if DoCheckForUpdates then kmcom.Options['koCheckForUpdates'].Value := True; // I2753
if DoAutomaticUpdates then kmcom.Options['koAutomaticUpdate'].Value := True;
if DoAutomaticallyReportUsage then kmcom.Options['koAutomaticallyReportUsage'].Value := True;

View file

@ -85,6 +85,7 @@ uses
Winapi.Windows,
keymanapi_TLB,
Sentry.Client,
XMLRenderer,
KeyboardListXMLRenderer,
UfrmKeymanBase,
@ -141,13 +142,14 @@ type
procedure Support_Diagnostics;
procedure Support_Online;
procedure Support_UpdateCheck;
procedure Support_ProxyConfig;
procedure Support_ContactSupport(params: TStringList); // I4390
procedure OpenSite(params: TStringList);
procedure DoApply;
procedure DoRefresh;
procedure Update_CheckNow;
procedure Update_ApplyNow;
protected
procedure FireCommand(const command: WideString; params: TStringList); override;
@ -184,12 +186,15 @@ uses
LanguagesXMLRenderer,
MessageIdentifierConsts,
MessageIdentifiers,
OnlineUpdateCheck,
Keyman.System.ExecutionHistory,
Keyman.System.KeymanSentryClient,
Keyman.System.RemoteUpdateCheck,
OptionsXMLRenderer,
Keyman.Configuration.System.UmodWebHttpServer,
Keyman.Configuration.System.HttpServer.App.ConfigMain,
Keyman.Configuration.UI.InstallFile,
Keyman.Configuration.UI.UfrmSettingsManager,
Keyman.Configuration.UI.UfrmStartInstallNow,
RegistryKeys,
SupportXMLRenderer,
UfrmChangeHotkey,
@ -202,12 +207,14 @@ uses
UfrmTextEditor,
uninstall,
Upload_Settings,
UpdateXMLRenderer,
utildir,
utilexecute,
utilkmshell,
utilhttp,
utiluac,
utilxml;
utilxml,
KeymanPaths;
type
PHKL = ^HKL;
@ -301,6 +308,7 @@ begin
FXMLRenderers.Add(TOptionsXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TLanguagesXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TSupportXMLRenderer.Create(FXMLRenderers));
FXMLRenderers.Add(TUpdateXMLRenderer.Create(FXMLRenderers));
xml := FXMLRenderers.RenderToString(s);
sharedData.Init(
@ -342,9 +350,11 @@ begin
else if command = 'support_diagnostics' then Support_Diagnostics
else if command = 'support_online' then Support_Online
else if command = 'support_updatecheck' then Support_UpdateCheck
else if command = 'support_proxyconfig' then Support_ProxyConfig
else if command = 'update_checknow' then Update_CheckNow
else if command = 'update_applynow' then Update_ApplyNow
else if command = 'contact_support' then Support_ContactSupport(params) // I4390
else if command = 'opensite' then OpenSite(params)
@ -789,29 +799,49 @@ begin
end;
end;
procedure TfrmMain.Support_UpdateCheck;
procedure TfrmMain.Update_CheckNow;
// TODO: epic-windows-update
// Get an instance to the state machine and call handle check so the state can change to update
// available.
var UpdateCheck : TRemoteUpdateCheck;
begin
with TOnlineUpdateCheck.Create(Self, True, False) do
UpdateCheck := TRemoteUpdateCheck.Create(True);
try
case Run of
oucShutDown:
begin
try
if kmcom.Control.IsKeymanRunning then
try
kmcom.Control.StopKeyman;
except
on E:Exception do KL.Log(E.Message);
end;
except
on E:Exception do KL.Log(E.Message);
end;
end;
oucSuccess:
DoRefresh;
end
UpdateCheck.Run;
finally
Free;
UpdateCheck.Free;
end;
DoRefresh;
end;
procedure TfrmMain.Update_ApplyNow;
var
ShellPath : string;
FResult, InstallNow: Boolean;
frmStartInstallNow: TfrmStartInstallNow;
begin
InstallNow := True;
// Confirm User is ok that this will require a reset
if HasKeymanRun then
begin
frmStartInstallNow := TfrmStartInstallNow.Create(nil);
try
if frmStartInstallNow.ShowModal = mrOk then
InstallNow := True
else
InstallNow := False;
finally
frmStartInstallNow.Free;
end;
end;
if InstallNow = True then
begin
ShellPath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell);
FResult := TUtilExecute.Shell(0, ShellPath, '', '-an');
if not FResult then
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
'TrmfMain: Shell Execute Update_ApplyNow Failed');
end;
end;

View file

@ -80,8 +80,12 @@ type
fmUninstallPackage, fmRegistryAdd, fmRegistryRemove,
fmMain, fmHelp, fmHelpKMShell,
fmMigrate, fmSplash, fmStart,
fmUpgradeKeyboards, fmOnlineUpdateCheck,// I2548
fmOnlineUpdateAdmin, fmTextEditor,
fmUpgradeKeyboards, // I2548
fmTextEditor,
fmInstallKeyboardPackageAdmin,
fmBackgroundUpdateCheck,
fmBackgroundDownload,
fmApplyInstallNow,
fmFirstRun, // I2562
fmKeyboardWelcome, // I2569
fmKeyboardPrint, // I2329
@ -113,13 +117,14 @@ uses
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.System.UImportOlderVersionKeyboards11To13,
Keyman.Configuration.UI.UfrmSettingsManager,
Keyman.Configuration.UI.UfrmStartInstall,
Keyman.System.KeymanStartTask,
KeymanPaths,
KLog,
kmint,
KMShellHints,
KeymanMutex,
OnlineUpdateCheck,
Keyman.System.RemoteUpdateCheck,
RegistryKeys,
UfrmBaseKeyboard,
UfrmKeymanBase,
@ -141,6 +146,7 @@ uses
UpgradeMnemonicLayout,
utilfocusappwnd,
utilkmshell,
Keyman.System.UpdateStateMachine,
KeyboardTIPCheck,
@ -240,7 +246,7 @@ begin
else if s = '-uk' then FMode := fmUninstallKeyboard { I1201 - Fix crash uninstalling admin-installed keyboards and packages }
else if s = '-ukl' then FMode := fmUninstallKeyboardLanguage // I3624
else if s = '-up' then FMode := fmUninstallPackage { I1201 - Fix crash uninstalling admin-installed keyboards and packages }
else if s = '-ou' then FMode := fmOnlineUpdateAdmin { I1730 - Check update of keyboards (admin elevation) }
else if s = '-ikp' then FMode := fmInstallKeyboardPackageAdmin
else if s = '-a' then FMode := fmAbout
else if s = '-ra' then FMode := fmRegistryAdd
else if s = '-rr' then FMode := fmRegistryRemove
@ -248,7 +254,9 @@ begin
else if s = '-?' then FMode := fmHelpKMShell
else if s = '-h' then FMode := fmHelp
else if s = '-t' then FMode := fmTextEditor
else if s = '-ouc' then FMode := fmOnlineUpdateCheck
else if s = '-buc' then FMode := fmBackgroundUpdateCheck
else if s = '-bd' then FMode := fmBackgroundDownload
else if s = '-an' then FMode := fmApplyInstallNow
else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
@ -381,6 +389,9 @@ var
kdl: IKeymanDefaultLanguage;
FIcon: string;
FMutex: TKeymanMutex; // I2720
BUpdateSM : TUpdateStateMachine;
frmStartInstall: TfrmStartInstall;
UserCanceled : Boolean;
function FirstKeyboardFileName: WideString;
begin
if KeyboardFileNames.Count = 0
@ -428,6 +439,55 @@ begin
Exit;
end;
BUpdateSM := TUpdateStateMachine.Create(False);
try
if (FMode = fmBackgroundUpdateCheck) then
begin
BUpdateSM.HandleCheck;
Exit;
end
else if (FMode = fmBackgroundDownload) then
begin
BUpdateSM.HandleDownload;
Exit;
end
else if (FMode = fmApplyInstallNow) then
begin
BUpdateSM.HandleInstallNow;
Exit;
end
else if (FMode = fmInstallKeyboardPackageAdmin) then
begin
BUpdateSM.HandleInstallPackages;
Exit;
end
else
begin
// The following logic around the WaitingRestartState should be
// encapsulated in the state machine however as we want separation of
// UI elements from the state machine we have bring some of logic here.
UserCanceled := False;
if BUpdateSM.ReadyToInstall and
(not FSilent and (FMode in [fmStart, fmSplash, fmMain, fmAbout])) then
begin
frmStartInstall := TfrmStartInstall.Create(nil);
try
if frmStartInstall.ShowModal = mrOk then
UserCanceled := False
else
UserCanceled := True
finally
frmStartInstall.Free;
end;
end;
if not UserCanceled and (BUpdateSM.HandleKmShell = 1) then
Exit;
end;
finally
BUpdateSM.Free;
end;
if not FSilent or (FMode = fmUpgradeMnemonicLayout) then // I4553
begin
// Note: will elevate and re-run if required
@ -469,17 +529,6 @@ begin
then ExitCode := 0
else ExitCode := 2;
fmOnlineUpdateAdmin:
OnlineUpdateAdmin(nil, FirstKeyboardFileName);
fmOnlineUpdateCheck:
with TOnlineUpdateCheck.Create(nil, FForce, FSilent) do
try
Run;
finally
Free;
end;
fmUpgradeKeyboards:// I2548
begin
if FQuery='13,backup' then
@ -619,6 +668,7 @@ begin
Pos('installdefaults', FQuery) > 0,
Pos('startwithwindows', FQuery) > 0,
Pos('checkforupdates', FQuery) > 0,
Pos('automaticupdates', FQuery) > 0,
FDisablePackages,
FDefaultUILanguage,
Pos('automaticallyreportusage', FQuery) > 0); // I2651, I2753

View file

@ -0,0 +1,94 @@
(*
Name: UpdateXMLRenderer
Copyright: Copyright (C) SIL International.
*)
unit UpdateXMLRenderer;
interface
uses
XMLRenderer,
Windows;
type
TUpdateXMLRenderer = class(TXMLRenderer)
protected
function XMLData: WideString; override;
end;
implementation
uses
StrUtils,
SysUtils,
VersionInfo,
kmint,
KeymanVersion,
keymanapi_TLB,
Keyman.System.LocaleStrings,
Keyman.System.UpdateCheckResponse,
Keyman.System.UpdateCheckStorage,
MessageIdentifierConsts,
MessageIdentifiers,
utilxml;
{ TUpdateXMLRenderer }
function TUpdateXMLRenderer.XMLData: WideString;
var
xml: string;
ucr: TUpdateCheckResponse;
i, n : Integer;
pkg: IKeymanPackage;
begin
xml := '';
if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then
begin
if (ucr.InstallURL <> '') then
begin
xml := xml +
'<Update>'+
'<index>0</index>'+
IfThen(not kmcom.SystemInfo.IsAdministrator, '<RequiresAdmin />')+
'<Keyman>'+
'<Text>'+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [ucr.NewVersion]))+'</Text>'+
'<NewVersion>'+xmlencode(ucr.NewVersion)+'</NewVersion>'+
'<OldVersion>'+xmlencode(ucr.CurrentVersion)+'</OldVersion>'+
'<DownloadSize>'+xmlencode(Format('%d', [ucr.InstallSize div 1024]))+'KB</DownloadSize>'+
'<DownloadURL>'+xmlencode(ucr.InstallURL)+'</DownloadURL>'+
'</Keyman>'+
'</Update>';
end;
for i := 0 to High(ucr.Packages) do
begin
n := kmcom.Packages.IndexOf(ucr.Packages[i].ID);
if n >= 0 then
begin
pkg := kmcom.Packages[n];
xml := xml +
'<Update>'+
'<index>'+IntToStr(i+1)+'</index>'+
IfThen(not kmcom.SystemInfo.IsAdministrator, '<RequiresAdmin />')+
'<Package>'+
'<Text>'+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText,
[ucr.Packages[i].Name, ucr.Packages[i].NewVersion]))+'</Text>'+
'<NewVersion>'+xmlencode(ucr.Packages[i].NewVersion)+'</NewVersion>'+
'<OldVersion>'+xmlencode(pkg.version)+'</OldVersion>'+
'<DownloadSize>'+xmlencode(Format('%d', [ucr.Packages[i].DownloadSize div 1024]))+'KB</DownloadSize>'+
'<DownloadURL>'+xmlencode(ucr.Packages[i].DownloadURL)+'</DownloadURL>'+
'</Package>'+
'</Update>';
pkg := nil;
end;
// else Package not found, skip
end;
end;
Result := '<Updates>'+xml+'</Updates>';
end;
end.

View file

@ -90,7 +90,6 @@ uses
MessageIdentifierConsts,
MessageIdentifiers,
KeymanMutex,
OnlineUpdateCheck,
PngImage,
ErrorControlledRegistry,
RegistryKeys,
@ -268,8 +267,7 @@ begin
if kmcom.Options[KeymanOptionName(TUtilKeymanOption.koCheckForUpdates)].Value then
begin
if not kmcom.Control.IsOnlineUpdateCheckOpen then
RunConfiguration(0, '-ouc -s');
RunConfiguration(0, '-buc -s');
end;
end;
end;

View file

@ -1,18 +1,18 @@
(*
Name: UfrmDownloadProgress
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 4 Dec 2006
Modified Date: 18 May 2012
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 04 Dec 2006 - mcdurdin - Initial version
05 Dec 2006 - mcdurdin - Localize caption
15 Jan 2007 - mcdurdin - Use font from locale.xml

View file

@ -33,7 +33,7 @@ unit utilkmshell; // I3306 // I4181
interface
uses
System.UITypes,
System.UITypes, System.IOUtils, System.Types,
Dialogs, Windows, ComObj, shlobj, controls, sysutils, classes;
const
@ -96,6 +96,7 @@ procedure SplitString(const instr: string; var outstr1, outstr2: string; const s
function ValidDirectory(const dir: string): string;
function GetLongFile(APath:String):String;
procedure GetFileNamesInDirectory(const directoryPath: string; var fileNames: TStringDynArray);
function TSFInstalled: Boolean;
@ -540,6 +541,19 @@ begin
until Length(APath)=0;
end; {Peter Haas}
procedure GetFileNamesInDirectory(const directoryPath: string; var fileNames: TStringDynArray);
begin
// Check if the directory exists
if TDirectory.Exists(directoryPath) then
begin
// Retrieve file names within the directory
fileNames := TDirectory.GetFiles(directoryPath);
end
else
KL.Log('Directory does not exist.');
end;
{ TString }
constructor TString.Create(const AString: string);

View file

@ -499,6 +499,20 @@ table tr
padding: 1px 0 1px 10px;
}
.grid_container_update {
display: grid;
position: relative;
grid-template-columns: 50px 1fr 1fr 1fr;
margin: 10px 50px 10px 64px;
/* From https://geary.co/internal-borders-css-grid/ */
overflow: hidden;
gap: var(--gap);
--gap: 2em;
--line-offset: calc(var(--gap) / 2);
--line-thickness: 1px;
--line-color: black;
}
.grid_container.grid_disabled
{
opacity: 0.5;
@ -508,11 +522,36 @@ table tr
{
position: relative;
font-size: 12px;
margin: 0px;
padding: 1px;
margin: 0 0 0 5px;
}
.grid_item::before,
.grid_item::after
{
content: '';
position: absolute;
background-color: var(--line-color);
z-index: 1;
}
/* row borders */
.grid_item::after
{
inline-size: 100vw;
block-size: var(--line-thickness);
inset-inline-start: 0;
inset-block-start: calc(var(--line-offset) * -1);
}
/* column borders */
.grid_item::before
{
inline-size: var(--line-thickness);
block-size: 100vh;
inset-inline-start: calc(var(--line-offset) * -1);
}
.grid_item_title{
font-weight: bold;
}
@ -1099,17 +1138,42 @@ th
height: 16px;
}
#keepintouch_content {
height: 100%;
overflow: hidden;
.update_title
{
background: url('keyman-title.png') 96px 13px no-repeat;
height: 60px;
}
#keepintouch_frame {
box-sizing: border-box;
width:100%;
height:100%;
border:none;
user-select: none;
.update_title img
{
margin: 13px 0 0 10px;
}
.update_edition
{
float: left;
font-size: 16px;
margin-left: 64px;
}
#update_status
{
font-size: 13px;
margin-left: 64px;
margin-top: 30px;
}
#update_content {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 5px 10px 5px 10px;
}
.update_controls
{
margin-left: 64px;
}
/* QRCodes */

View file

@ -35,7 +35,7 @@ function windowResize()
e = _$('subcontent_pro');
if(e) e.style.height = h;
_$('subcontent_support').style.height = h;
_$('subcontent_keepintouch').style.height = h;
_$('subcontent_update').style.height = h;
}
}

View file

@ -12,7 +12,7 @@
<xsl:include href="keyman_options.xsl"/>
<xsl:include href="keyman_hotkeys.xsl"/>
<xsl:include href="keyman_support.xsl"/>
<xsl:include href="keyman_keepintouch.xsl"/>
<xsl:include href="keyman_update.xsl"/>
<xsl:include href="keyman_footer.xsl"/>
@ -50,7 +50,7 @@
<div class="contentpage" id="content_options"><xsl:call-template name="content_options" /></div>
<div class="contentpage" id="content_hotkeys"><xsl:call-template name="content_hotkeys" /></div>
<div class="contentpage" id="content_support"><xsl:call-template name="content_support" /></div>
<div class="contentpage" id="content_keepintouch"><xsl:call-template name="content_keepintouch" /></div>
<div class="contentpage" id="content_update"><xsl:call-template name="content_update" /></div>
</div>
<div id="footerframe">
<xsl:call-template name="footerframe" />

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="content_keepintouch">
<script>
window['menuframe_activate_keepintouch'] = function() {
document.getElementById('keepintouch_frame').src = '<xsl:value-of select='/Keyman/keyman-com' />/go/windows/<xsl:value-of select="/Keyman/version-info/@versionRelease" />/keep-in-touch?embed=1';
}
</script>
<div class="header">
<xsl:call-template name="header_helplinks" />
<xsl:value-of select="$locale/string[@name='S_KeepInTouch']"/>
</div>
<div id="subcontent_keepintouch" class="content">
<div id="keepintouch_content">
<iframe id='keepintouch_frame'></iframe>
</div>
</div>
</xsl:template>
</xsl:stylesheet>

View file

@ -9,20 +9,20 @@
<xsl:variable name="locale_Options"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Options']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Hotkeys"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Hotkeys']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Support"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Support']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_KeepInTouch"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_KeepInTouch']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Update"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Update']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Keyboards_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Keyboards_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Options_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Options_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Hotkeys_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Hotkeys_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Support_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Support_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_KeepInTouch_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_KeepInTouch_AccessChar']" /></xsl:call-template></xsl:variable>
<xsl:variable name="locale_Update_AccessChar"><xsl:call-template name="escape-apos"><xsl:with-param name="text" select="$locale/string[@name='S_Update_AccessChar']" /></xsl:call-template></xsl:variable>
<script type="text/javascript">
menuframe_add('/app/', 'keyboardlist', '<xsl:value-of select="$locale_Keyboards"/>', '<xsl:value-of select="$locale_Keyboards_AccessChar"/>');
menuframe_add('/app/', 'options', '<xsl:value-of select="$locale_Options"/>', '<xsl:value-of select="$locale_Options_AccessChar"/>');
menuframe_add('/app/', 'hotkeys', '<xsl:value-of select="$locale_Hotkeys"/>', '<xsl:value-of select="$locale_Hotkeys_AccessChar"/>');
menuframe_add('/app/', 'support', '<xsl:value-of select="$locale_Support"/>', '<xsl:value-of select="$locale_Support_AccessChar"/>');
menuframe_add('/app/', 'keepintouch', '<xsl:value-of select="$locale_KeepInTouch"/>', '<xsl:value-of select="$locale_KeepInTouch_AccessChar"/>');
menuframe_add('/app/', 'update', '<xsl:value-of select="$locale_Update"/>', '<xsl:value-of select="$locale_Update_AccessChar"/>');
</script>
<span class="menuframe" id="menuframe_footer"><xsl:text></xsl:text></span>
</xsl:template>

View file

@ -49,7 +49,6 @@
<ul>
<li><a><xsl:attribute name="href">keyman:link?url=<xsl:value-of select="/Keyman/keyman-com"/>/</xsl:attribute>keyman.com</a></li>
<li><a href="keyman:support_diagnostics"><xsl:value-of select="$locale/string[@name='S_Menu_Diagnostics_Diagnostics']"/></a></li>
<li><a href="keyman:support_updatecheck"><xsl:value-of select="$locale/string[@name='S_Button_CheckForUpdates']"/></a></li>
<li><a>
<xsl:attribute name="href">keyman:link?url=<xsl:value-of select="/Keyman/keyman-com" />/go/<xsl:value-of select="/Keyman/version-info/@versionRelease" />/support</xsl:attribute
><xsl:value-of select="$locale/string[@name='S_Button_OnlineSupport']"/></a></li>

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="content_update">
<xsl:variable name="isNewKeymanVersionAvailable" select="boolean(/Keyman/Updates/Update/Keyman/NewVersion)"/>
<xsl:variable name="isNewKeyboardVersionAvailable" select="boolean(/Keyman/Updates/Update/Package/NewVersion)"/>
<div class="header">
<xsl:call-template name="header_helplinks" />
<xsl:value-of select="$locale/string[@name='S_Update']"/>
</div>
<div id="subcontent_update" class="content">
<div id="update_content">
<div class="update_title">
<img src="/app/keyman-desktop.png" />
</div>
<div class="update_edition">
<xsl:value-of select="$locale/string[@name='S_Support_Version']"/>&#160;<xsl:value-of select="/Keyman/version-info/@versionWithTag" />
</div>
<div id="update_status">
<xsl:if test="$isNewKeymanVersionAvailable or $isNewKeyboardVersionAvailable">
Updates are available which will be applied when Windows is next restarted:
</xsl:if>
<xsl:if test="not($isNewKeymanVersionAvailable or $isNewKeyboardVersionAvailable)">
No updates are available.
</xsl:if>
</div>
<div class="grid_container_update" id="update_details">
<div class='grid_item'>Select</div>
<div class='grid_item'><xsl:value-of select="$locale/string[@name='S_Update_ComponentHead']"/></div>
<div class='grid_item'><xsl:value-of select="$locale/string[@name='S_Update_OldVersionHead']"/></div>
<div class='grid_item'><xsl:value-of select="$locale/string[@name='S_Update_SizeHead']"/></div>
<xsl:apply-templates select="/Keyman/Updates/Update" />
</div>
<div class="update_controls" id="update_controls">
<xsl:choose>
<xsl:when test="$isNewKeymanVersionAvailable or $isNewKeyboardVersionAvailable">
<xsl:call-template name="button">
<xsl:with-param name="caption"><xsl:value-of select="$locale/string[@name='S_Button_Update_ApplyNow']"/></xsl:with-param>
<xsl:with-param name="command">keyman:update_applynow</xsl:with-param>
<xsl:with-param name="width">220px</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="button">
<xsl:with-param name="caption"><xsl:value-of select="$locale/string[@name='S_Button_Update_ApplyNow']"/></xsl:with-param>
<xsl:with-param name="command"></xsl:with-param>
<xsl:with-param name="width">220px</xsl:with-param>
<xsl:with-param name="disabled">1</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="button">
<xsl:with-param name="caption"><xsl:value-of select="$locale/string[@name='S_Button_Update_CheckNow']"/></xsl:with-param>
<xsl:with-param name="command">keyman:update_checknow</xsl:with-param>
<xsl:with-param name="width">220px</xsl:with-param>
</xsl:call-template>
</div>
<!-- <li><a href="keyman:support_updatecheck"><xsl:value-of select="$locale/string[@name='S_Button_CheckForUpdates']"/></a></li> -->
</div>
</div>
</xsl:template>
<xsl:template match="/Keyman/Updates/Update">
<div class="UpdateCheckBox grid_item">
<input type="checkbox">
<xsl:attribute name="onclick">javascript:updateTick("<xsl:value-of select="index" />");</xsl:attribute>
<xsl:attribute name="id">Update_<xsl:value-of select="index" /></xsl:attribute>
<xsl:if test="not(@DefaultUnchecked)"><xsl:attribute name="checked">checked</xsl:attribute></xsl:if>
<xsl:attribute name="name">Update_<xsl:value-of select="index" /></xsl:attribute>
</input>
<xsl:if test="RequiresAdmin">
<span><xsl:attribute name="id">Update_<xsl:value-of select="index" />_RequiresAdmin</xsl:attribute></span>
</xsl:if>
</div>
<xsl:choose>
<xsl:when test="Package/Text != ''">
<div class='grid_item'>
<label>
<xsl:attribute name="for">Update_<xsl:value-of select="index"/></xsl:attribute>
<xsl:value-of select="Package/Text"/>
</label>
</div>
<div class='grid_item'>
<xsl:value-of select="Package/OldVersion"/>
</div>
<div class='grid_item'>
<xsl:value-of select="Package/DownloadSize"/>
</div>
</xsl:when>
<xsl:when test="Keyman/DownloadURL != ''">
<div class='grid_item'>
<label>
<xsl:attribute name="for">Update_<xsl:value-of select="index"/></xsl:attribute>
<xsl:value-of select="Keyman/Text"/>
</label>
</div>
<div class='grid_item'>
<xsl:value-of select="Keyman/OldVersion"/>
</div>
<div class='grid_item'>
<xsl:value-of select="Keyman/DownloadSize"/>
</div>
</xsl:when>
<xsl:otherwise>
<div class='grid_item'>
<div id="DownloadFrom">
<xsl:value-of select="$locale/string[@name='S_Update_DownloadFrom']"/>
</div>
<div id="URL">
<a href="keyman:openwebsite">
<xsl:value-of select="/Keyman/DownloadURL"/>
</a>
</div>
</div>
<div class='grid_item'>
<xsl:value-of select="Keyman/DownloadVersion"/>
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -417,6 +417,11 @@
<!-- Introduced: 7.0.230.0 -->
<string name="koShowWelcome" comment="Startup options - show/hide welcome screen">Show welcome screen</string>
<!-- Context: Configuration Dialog - Options tab -->
<!-- String Type: FormatString -->
<!-- Introduced: 18.0.96.0 -->
<string name="koAutomaticUpdate" comment="Automatically check for updates and download">Automatically check for updates and download</string>
<!-- Context: Configuration Dialog - Options tab -->
<!-- String Type: FormatString -->
<!-- Introduced: 7.0.230.0 -->
@ -586,8 +591,6 @@
<!-- Introduced: 7.0.230.0 -->
<string name="S_Menu_Diagnostics_Diagnostics" comment="Support button submenu - performs a diagnostic report">Diagnostics</string>
<!-- Context: Download Keyboard Dialog -->
<!-- String Type: PlainText -->
<!-- Introduced: 7.0.230.0 -->
@ -712,6 +715,10 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to
<!-- Context: Online Update Dialog -->
<!-- String Type: PlainText -->
<!-- Introduced: 18.0.230.0 -->
<string name="S_Update" comment="Update tab name">Update</string>
<!-- Context: Online Update Dialog -->
<!-- String Type: PlainText -->
@ -803,6 +810,16 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to
<!-- Context: Update Dialog -->
<!-- String Type: FormatString -->
<!-- Introduced: 17.0233.0 -->
<string name="S_Button_Update_ApplyNow" comment="Apply update now button - update dialog">Apply update now</string>
<!-- Context: Update Dialog -->
<!-- String Type: FormatString -->
<!-- Introduced: 17.0233.0 -->
<string name="S_Button_Update_CheckNow" comment="Check for updates now button - update dialog">Check for new updates</string>
<!-- Context: Splash Dialog -->
<!-- String Type: PlainText -->

View file

@ -82,7 +82,7 @@ type
InstallSuccess: Boolean);
function InstallMSI(msiLocation: TInstallInfoFileLocation; var InstallDefaults: Boolean; ContinueSetup: Boolean): Boolean;
procedure ConfigFirstRun(StartKeyman,StartWithWindows,
CheckForUpdates,StartDisabled,StartWithConfiguration,InstallDefaults,
CheckForUpdates,AutomaticUpdates,StartDisabled,StartWithConfiguration,InstallDefaults,
AutomaticallyReportUsage: Boolean);
procedure PrepareForReboot(res: Cardinal; InstallDefaults: Boolean);
function RestartWindows: Boolean;
@ -101,7 +101,7 @@ type
destructor Destroy; override;
procedure CheckInternetConnectedState;
function DoInstall(Handle: THandle;
StartAfterInstall, StartWithWindows, CheckForUpdates, StartDisabled,
StartAfterInstall, StartWithWindows, CheckForUpdates, AutomaticUpdates, StartDisabled,
StartWithConfiguration, InstallDefaults, AutomaticallyReportUsage, ContinueSetup: Boolean): Boolean;
procedure LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean = True);
procedure LogInfo(const msg: string; ShowDialogIfNotSilent: Boolean = False);
@ -194,7 +194,7 @@ begin
end;
function TRunTools.DoInstall(Handle: THandle;
StartAfterInstall, StartWithWindows, CheckForUpdates, StartDisabled,
StartAfterInstall, StartWithWindows, CheckForUpdates, AutomaticUpdates, StartDisabled,
StartWithConfiguration, InstallDefaults, AutomaticallyReportUsage, ContinueSetup: Boolean): Boolean;
var
msiLocation: TInstallInfoFileLocation;
@ -224,7 +224,7 @@ begin
Exit(False);
end;
ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates,
ConfigFirstRun(StartAfterInstall,StartWithWindows,CheckForUpdates,AutomaticUpdates,
StartDisabled,StartWithConfiguration,InstallDefaults,AutomaticallyReportUsage);
Result := True;
@ -588,7 +588,7 @@ begin
end;
end;
procedure TRunTools.ConfigFirstRun(StartKeyman,StartWithWindows,CheckForUpdates,
procedure TRunTools.ConfigFirstRun(StartKeyman,StartWithWindows,CheckForUpdates,AutomaticUpdates,
StartDisabled,StartWithConfiguration,InstallDefaults,AutomaticallyReportUsage: Boolean);
var
i: Integer;
@ -686,6 +686,7 @@ begin
if StartWithWindows then s := s + 'StartWithWindows,';
if CheckForUpdates then s := s + 'CheckForUpdates,';
if AutomaticUpdates then s := s + 'AutomaticUpdates,';
if AutomaticallyReportUsage then s := s + 'AutomaticallyReportUsage,';
if InstallDefaults then

View file

@ -113,6 +113,7 @@ type
FCanUpgrade9: Boolean;
FCanUpgrade10: Boolean;
FCheckForUpdates: Boolean;
FAutomaticUpdates: Boolean;
FStartAfterInstall: Boolean;
FStartWithWindows: Boolean;
FAutomaticallyReportUsage: Boolean;
@ -523,7 +524,7 @@ begin
SetupMSI; // I2644
if GetRunTools.DoInstall(Handle, FStartAfterInstall, FStartWithWindows, FCheckForUpdates,
FInstallInfo.StartDisabled, FInstallInfo.StartWithConfiguration, FInstallDefaults,
FAutomaticUpdates, FInstallInfo.StartDisabled, FInstallInfo.StartWithConfiguration, FInstallDefaults,
FAutomaticallyReportUsage, FContinueSetup) then
begin
if not Silent and not FStartAfterInstall then // I2610
@ -1032,6 +1033,7 @@ procedure TfrmRunDesktop.GetDefaultSettings; // I2651
begin
FStartWithWindows := True; // I2607
FCheckForUpdates := True; // I2609
FAutomaticUpdates := True;
try
with CreateHKCURegistry do // I2749
@ -1041,6 +1043,8 @@ begin
FCheckForUpdates := ValueExists(SRegValue_CheckForUpdates) and ReadBool(SRegValue_CheckForUpdates);
FStartWithWindows := ValueExists(SRegValue_UpgradeRunKeyman) or
(OpenKeyReadOnly('\' + SRegKey_WindowsRun_CU) and ValueExists(SRegValue_WindowsRun_Keyman));
FAutomaticUpdates := not ValueExists(SRegValue_AutomaticUpdates) or ReadBool(SRegValue_AutomaticUpdates);
end
else if FCanUpgrade10 and OpenKeyReadOnly(SRegKey_KeymanEngine100_ProductOptions_Desktop_CU) then // I4293
begin

View file

@ -27,10 +27,10 @@ object frmKeyman7Main: TfrmKeyman7Main
Left = 28
Top = 40
end
object tmrOnlineUpdateCheck: TTimer
object tmrBackgroundUpdateCheck: TTimer
Enabled = False
Interval = 300000
OnTimer = tmrOnlineUpdateCheckTimer
OnTimer = tmrBackgroundUpdateCheckTimer
Left = 280
Top = 104
end

View file

@ -198,13 +198,13 @@ type
TfrmKeyman7Main = class(TForm)
mnu: TPopupMenu;
tmrTestKeymanFunctioning: TTimer;
tmrOnlineUpdateCheck: TTimer;
tmrBackgroundUpdateCheck: TTimer;
tmrCheckInputPane: TTimer;
tmrRefresh: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure tmrTestKeymanFunctioningTimer(Sender: TObject);
procedure tmrOnlineUpdateCheckTimer(Sender: TObject);
procedure tmrBackgroundUpdateCheckTimer(Sender: TObject);
procedure tmrCheckInputPaneTimer(Sender: TObject);
procedure tmrRefreshTimer(Sender: TObject);
private
@ -1838,7 +1838,7 @@ begin
end;
end;
procedure TfrmKeyman7Main.tmrOnlineUpdateCheckTimer(Sender: TObject);
procedure TfrmKeyman7Main.tmrBackgroundUpdateCheckTimer(Sender: TObject);
begin
with TRegistryErrorControlled.Create do // I2890
try
@ -1846,7 +1846,7 @@ begin
begin
if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) then Exit;
if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) then Exit;
TKeymanDesktopShell.RunKeymanConfiguration('-ouc');
TKeymanDesktopShell.RunKeymanConfiguration('-buc');
end;
finally
Free;

View file

@ -32,7 +32,7 @@ uses
UfrmOSKPlugInBase in 'viskbd\UfrmOSKPlugInBase.pas' {frmOSKPlugInBase},
UfrmOSKCharacterMap in 'viskbd\UfrmOSKCharacterMap.pas' {frmOSKCharacterMap},
UfrmOSKEntryHelper in 'viskbd\UfrmOSKEntryHelper.pas' {frmOSKEntryHelper},
TTInfo in '..\..\..\..\common\windows\delphi\general\TTInfo.pas',
ttinfo in '..\..\..\..\common\windows\delphi\general\ttinfo.pas',
UnicodeData in '..\..\..\..\common\windows\delphi\charmap\UnicodeData.pas',
CharacterMapSettings in '..\..\..\..\common\windows\delphi\charmap\CharacterMapSettings.pas',
CharacterRanges in '..\..\..\..\common\windows\delphi\charmap\CharacterRanges.pas',
@ -112,7 +112,8 @@ uses
Sentry.Client.Vcl in '..\..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.Vcl.pas',
sentry in '..\..\..\..\common\windows\delphi\ext\sentry\sentry.pas',
Keyman.System.KeymanSentryClient in '..\..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas',
Keyman.System.LocaleStrings in '..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas';
Keyman.System.LocaleStrings in '..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas',
Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas';
{$R ICONS.RES}
{$R VERSION.RES}

View file

@ -141,7 +141,7 @@
<DCCReference Include="viskbd\UfrmOSKEntryHelper.pas">
<Form>frmOSKEntryHelper</Form>
</DCCReference>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\TTInfo.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\ttinfo.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\charmap\UnicodeData.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\charmap\CharacterMapSettings.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\charmap\CharacterRanges.pas"/>
@ -238,6 +238,7 @@
<DCCReference Include="..\..\..\..\common\windows\delphi\ext\sentry\sentry.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas"/>
<DCCReference Include="..\..\global\delphi\cust\Keyman.System.LocaleStrings.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas"/>
<None Include="Profiling\AQtimeModule1.aqt"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
@ -299,21 +300,21 @@
<Platform value="Win64">False</Platform>
</Platforms>
<Deployment Version="3">
<DeployFile LocalName="keyman.exe" Configuration="Debug" Class="ProjectOutput">
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteName>keyman.exe</RemoteName>
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="keyman.rsm" Configuration="Debug" Class="DebugSymbols">
<DeployFile LocalName="bin\Win32\Debug\keyman.rsm" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win32">
<RemoteName>keyman.rsm</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
<DeployFile LocalName="bin\Win32\Debug\keyman.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<RemoteName>keyman.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>

View file

@ -40,9 +40,11 @@ uses
System.Win.Registry,
GetOsVersion,
Keyman.System.ExecutionHistory,
Keyman.System.Security,
Keyman.Winapi.VersionHelpers,
KeymanVersion,
Klog,
RegistryKeys,
UfrmKeyman7Main,
UserMessages;
@ -76,9 +78,10 @@ var
hMutex: Cardinal;
begin
if not ValidateParameters(FCommand) then Exit;
RecordKeymanStarted;
hProgramMutex := CreateMutex(nil, False, 'KeymanEXE70');
if hProgramMutex = 0 then
begin

View file

@ -429,7 +429,7 @@ end;
procedure TKeymanControl.OpenUpdateCheck;
begin
RunKeymanConfiguration('-ouc');
RunKeymanConfiguration('-buc');
end;
procedure TKeymanControl.StartKeyman;

View file

@ -121,7 +121,7 @@ type
GroupName: string;
end;
const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552
const KeymanOptionInfo: array[0..17] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552
// Global options
(opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'),
@ -129,7 +129,8 @@ const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I36
(opt: koAltGrCtrlAlt; RegistryName: SRegValue_AltGrCtrlAlt; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'),
(opt: koRightModifierHK; RegistryName: SRegValue_AllowRightModifierHotKey; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'),
(opt: koShowHints; RegistryName: SRegValue_EnableHints; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'),
(opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'),
(opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'),
(opt: koAutomaticUpdate; RegistryName: SRegValue_AutomaticUpdates; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'),
(opt: koAutomaticallyReportErrors; RegistryName: SRegValue_AutomaticallyReportErrors; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I4393
(opt: koAutomaticallyReportUsage; RegistryName: SRegValue_AutomaticallyReportUsage; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I4393

View file

@ -10,6 +10,7 @@ type
koRightModifierHK,
koReleaseShiftKeysAfterKeyPress,
koShowHints, // I1256
koAutomaticUpdate,
// Startup options
koTestKeymanFunctioning,
koStartWithWindows,