chore(web): Merge branch 'master' into refactor/web/osk-centralized-gestures

This commit is contained in:
jahorton 2021-07-01 13:40:11 +07:00
commit abbeff8faa
113 changed files with 2151 additions and 993 deletions

View file

@ -1,5 +1,43 @@
# Keyman Version History
## 15.0.78 alpha 2021-06-30
* fix(web): console errors when using SHIFT w help-text 'osk' (#5392)
## 15.0.77 alpha 2021-06-29
* fix(windows): #5336 appcontext get to handle small buffer (#5383)
* fix(common): get handle caller buffer size small than internal Fixes … (#5390)
## 15.0.76 alpha 2021-06-28
* feat(web): Add option useAlerts to control alerts() (#5302)
* chore(android): Add help on Settings option to change spacebar caption (#5375)
* feat(android): hide textarea for perf (#5376)
* chore: keyboard_info.source.json 1.0.6 (#5379)
* refactor(android): setKeyboard and setKeymanLanguage (#5338)
* feat(web): Merge keyboard stub with error stub (#5340)
* feat(linux): Add reference index page (#5370)
* fix(linux): Fix restarting ibus when running with sudo (#5371)
* refactor(linux): Centralize paths (#5372)
* fix(linux): Save QR code in temporary directory (#5373)
## 15.0.75 alpha 2021-06-25
* fix(android): rotation is not updating keyboard (#5247)
* fix(windows): osk scaling mismatch on horz axis (#5341)
* fix(linux): Don't add duplicate entries when reinstalling keyboard (#5363)
* fix(linux): fix tab completion for command line tools (#5362)
* feat(web): Update addLanguageKeyboards to return a Promise (#5260)
* feat(web): Add option useAlerts to control alerts() (#5302)
## 15.0.74 alpha 2021-06-24
* fix(android): fixes application of nextlayer for subkeys with customized layer setting (#5350)
* feat(common/core/web): engine correction-prep optimizations (#5319)
* feat(linux): Add support for impish (Ubuntu 21.10) (#5334)
* fix(linux): Add libglib2.0-bin to keyman Depends 🍒 (#5360)
## 15.0.73 alpha 2021-06-23
* chore(windows): add engine test framework (#5337)

View file

@ -1 +1 @@
15.0.74
15.0.79

View file

@ -41,7 +41,7 @@
ta.readOnly = false;
// Tell KMW the default banner height to use
com.keyman.osk.Banner.DEFAULT_HEIGHT =
com.keyman.osk.Banner.DEFAULT_HEIGHT =
Math.ceil(window.jsInterface.getDefaultBannerHeight() / window.devicePixelRatio);
kmw.addEventListener('keyboardloaded', setIsChiral);
@ -128,49 +128,10 @@
return true;
}
function setKeymanLanguage(keyboardName, internalName, languageName, langId, kbdFile, font, oskFont, package) {
var kmw=window['keyman'];
var kbdInterface=window.KeymanWeb;
// Defaults for missing arguments
switch (arguments.length) {
case 1:
internalName = keyboardName;
if (keyboardName == 'English') {
internalName = 'us';
}
break;
case 2:
languageName = keyboardName;
break;
case 3:
langId='';
break;
case 6:
oskFont = font;
break;
}
var k = {
KN:keyboardName,
KI:'Keyboard_'+internalName,
KLC:langId,
KL:languageName,
KF:kbdFile,
KP:package
};
if (font) {
k.KFont = font;
//window.console.log('keyboard.html assigning k.KFont = ' + font);
}
if (oskFont) {
k.KOskFont = oskFont;
//window.console.log('keyboard.html assigning k.KOskFont = ' + oskFont);
}
kbdInterface.registerStub(k);
kmw['setActiveKeyboard'](package + '::Keyboard_'+internalName,langId);
kmw['osk']['show'](true);
function setKeymanLanguage(k) {
KeymanWeb.registerStub(k);
keyman.setActiveKeyboard(k.KP + '::'+k.KI, k.KLC);
keyman.osk.show(true);
}
/**
@ -320,7 +281,7 @@
function executePopupKey(keyID, keyText) {
var kmw=window['keyman'];
// KMW only needs keyID to process the popup key. keyText merely logged to console
//window.console.log('executePopupKey('+keyID+'); keyText: ' + keyText);
kmw['executePopupKey'](keyID);
@ -366,8 +327,6 @@
</style>
</head>
<body class="kmw-embedded keyman-app">
<center>
<textarea id="ta" cols="1" rows="1" style="position:absolute; left:-500px; top:0px;"></textarea>
</center>
<textarea id="ta" cols="1" rows="1" style="display: none;"></textarea>
</body>
</html>

View file

@ -507,60 +507,37 @@ final class KMKeyboard extends WebView {
KMManager.getLatestKeyboardFileVersion(getContext(), packageID, keyboardID) : null;
}
if(kOskFont == null || kOskFont.isEmpty())
kOskFont = kFont;
JSONObject jDisplayFont = makeFontPaths(kFont);
JSONObject jOskFont = makeFontPaths(kOskFont);
txtFont = getFontFilename(jDisplayFont);
oskFont = getFontFilename(jOskFont);
String kbKey = KMString.format("%s_%s", languageID, keyboardID);
setKeyboardRoot(packageID);
String keyboardPath = makeKeyboardPath(packageID, keyboardID, keyboardVersion);
String tFont = "''";
String oFont = null;
if (kFont == null) {
txtFont = "";
} else {
if (FileUtils.hasFontExtension(kFont)) {
txtFont = kFont;
tFont = KMString.format("{\"family\":\"font_family_%s\",\"files\":[\"%s%s\"]}", kFont.substring(0, kFont.length() - 4), keyboardRoot, kFont);
} else {
txtFont = getFontFilename(kFont);
if (!txtFont.isEmpty()) {
tFont = makeFontPaths(kFont);
}
}
JSONObject reg = new JSONObject();
try {
reg.put("KN", keyboardName);
reg.put("KI", "Keyboard_" + keyboardID);
reg.put("KLC", languageID);
reg.put("KL", languageName);
reg.put("KF", keyboardPath);
reg.put("KP", packageID);
if (jDisplayFont != null) reg.put("KFont", jDisplayFont);
if (jOskFont != null) reg.put("KOskFont", jOskFont);
} catch(JSONException e) {
KMLog.LogException(TAG, "", e);
return false;
}
if (kOskFont == null || kOskFont.isEmpty()) {
oskFont = null;
} else {
if (FileUtils.hasFontExtension(kOskFont)) {
oskFont = kOskFont;
oFont = KMString.format("{\"family\":\"font_family_%s\",\"files\":[\"%s%s\"]}", kOskFont.substring(0, kOskFont.length() - 4), keyboardRoot, kOskFont);
} else {
oskFont = getFontFilename(kOskFont);
if (!oskFont.isEmpty()) {
oFont = makeFontPaths(kOskFont);
} else {
oskFont = null;
}
}
}
if (oFont == null) {
oFont = tFont;
}
if (tFont.equals("''")) {
tFont = fontUndefined;
}
if (oFont.equals("''")) {
oFont = fontUndefined;
}
// Escape single-quoted names for javascript call
keyboardName = keyboardName.replaceAll("\'", "\\\\'"); // Double-escaped-backslash b/c regex.
languageName = languageName.replaceAll("\'", "\\\\'");
String jsFormat = "setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')";
String jsString = KMString.format(jsFormat, keyboardName, keyboardID, languageName, languageID, keyboardPath, tFont, oFont, packageID);
String jsString = KMString.format("setKeymanLanguage(%s)", reg.toString());
loadJavascript(jsString);
this.packageID = packageID;
@ -695,17 +672,16 @@ final class KMKeyboard extends WebView {
/**
* getFontFilename
* Parse a Font JSON object and return the font filename (ending in .ttf or .otf)
* @param jsonString String - Font JSON object as a string
* @param fontObj JSONObject - Font JSON object
* @return String - Filename for the font. If font is invalid, return ""
*/
private String getFontFilename(String jsonString) {
private String getFontFilename(JSONObject fontObj) {
String font = "";
if (jsonString == null || jsonString.isEmpty()) {
if (fontObj == null) {
return font;
}
try {
JSONObject fontObj = new JSONObject(jsonString);
JSONArray sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontSource);
JSONArray sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontFiles);
if (sourceArray != null) {
String fontFile;
int length = sourceArray.length();
@ -717,7 +693,7 @@ final class KMKeyboard extends WebView {
}
}
} else {
String fontFile = fontObj.optString(KMManager.KMKey_FontSource);
String fontFile = fontObj.optString(KMManager.KMKey_FontFiles);
if (fontFile != null) {
if (FileUtils.hasFontExtension(fontFile)) {
font = fontFile;
@ -1058,10 +1034,24 @@ final class KMKeyboard extends WebView {
* 1. Replace "source" keys for "files" keys
* 2. Create full font paths for .ttf or .svg
* @param font String font JSON object as a string
* @return String of modified font information with full paths. If font is invalid, return "''"
* @return JSONObject of modified font information with full paths. If font is invalid, return `null`
*/
private String makeFontPaths(String font) {
private JSONObject makeFontPaths(String font) {
if(font == null) {
return null;
}
try {
if (FileUtils.hasFontExtension(font)) {
JSONObject jfont = new JSONObject();
jfont.put(KMManager.KMKey_FontFamily, font.substring(0, font.length()-4));
JSONArray jfiles = new JSONArray();
jfiles.put(keyboardRoot + font);
jfont.put(KMManager.KMKey_FontFiles, jfiles);
return jfont;
}
JSONObject fontObj = new JSONObject(font);
JSONArray sourceArray;
String fontFile;
@ -1076,7 +1066,7 @@ final class KMKeyboard extends WebView {
if (obj instanceof String) {
fontFile = fontObj.getString(KMManager.KMKey_FontFiles);
fontObj.put(KMManager.KMKey_FontFiles, keyboardRoot + obj);
return fontObj.toString();
return fontObj;
} else if (obj instanceof JSONArray) {
sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontFiles);
if (sourceArray != null) {
@ -1085,17 +1075,17 @@ final class KMKeyboard extends WebView {
if (FileUtils.hasFontExtension(fontFile)) {
fontObj.put(KMManager.KMKey_FontFiles, keyboardRoot + fontFile);
fontObj.remove(KMManager.KMKey_FontSource);
return fontObj.toString();
return fontObj;
}
}
}
}
} catch (JSONException e) {
KMLog.LogException(TAG, "", e);
return "''";
return null;
}
return font;
return null;
}
@SuppressLint("InflateParams")

View file

@ -190,6 +190,7 @@ public final class KMManager {
public static final String KMKey_OskFont = "oskFont";
public static final String KMKey_FontSource = "source";
public static final String KMKey_FontFiles = "files";
public static final String KMKey_FontFamily = "family";
public static final String KMKey_KeyboardModified = "lastModified";
public static final String KMKey_KeyboardRTL = "rtl";

View file

@ -1,78 +1,18 @@
# Keyman for Android & Keyman Engine for Android
## Prerequisites
* Android Studio 4.1+
* Java SE Development Kit 8
* [Node.js](https://nodejs.org/) 14.17+ (for building KeymanWeb)
* [Pandoc](#Install-Pandoc) for generating offline help
## Install Java
It is recommended to use openJDK because of oracle license issues.
Tested with latest release for openJDK 8 from
https://github.com/ojdkbuild/ojdkbuild
1. Download and unpack the zip archive
2. On on windows: use the default java path C:\Program Files\Java to avoid error message "Error 0x80010135 Path Too Long".
3. Aso set an environment variable for JAVA_HOME e.g C:\Program Files\Java\openjdk-1.8.0.232-1
See [build configuration](../docs/build/index.md) for details on how to configure your build environment.
## Keyman Minimum Android Requirements
Keyman for Android has a minSdkVersion of 21 for [Android 5.0 Lollipop](https://developer.android.com/about/versions/lollipop)
## Setup Android Studio
1. Download [Android Studio](https://developer.android.com/studio/index.html) and install with these [instructions](https://developer.android.com/studio/install.html).
2. For Windows users, set environment variable **ANDROID_HOME** to the location of your Android SDK. The default installation location is **C:\Users\\[USER]\AppData\Local\Android\sdk** where [USER] is your username. You may need to log out and log back in to take effect.
For MacOS/Linux users, add the following to **~/.bashrc** or **~/.bash_profile**
```bash
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/tools
```
For MacOS users, add the following (adjusted appropriately) to **~/.bashrc** or **~/.bash_profile** if your Java version is too strange for gradlew to understand (e.g., 11.0.2)
```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
echo $JAVA_HOME #should output: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
```
3. For Windows users, from a Git Bash Prompt window, cd to the **sdk/tools/bin** folder and accept all the SDK license agreements
```
yes | ./sdkmanager.bat --licenses
```
For MacOS users, from a Terminal window, cd to the **~/Library/Android/sdk/tools/bin** folder and accept all the SDK license agreements
```bash
yes | ./sdkmanager --licenses
```
4. If you plan to test on a physical device via USB, install the appropriate [OEM USB drivers](https://developer.android.com/studio/run/oem-usb.html)
5. Install [Java SE Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
## Keyman for Android Development
Keyman for Android (formerly named KMAPro) can be built from a command line (preferred) or Android Studio.
Building Keyman Web is a precursor for compiling KMEA, so verify your system has all the [Minimum Web Compilation Requirements](../web/README.md#minimum-web-compilation-requirements)
### Install JQ
jq 1.6+ is used during the build process to determine the latest versions of the default keyboard (sil_euro_latin.kmp) and lexical-model (en.nrc.mtnt.model.kmp) packages to download. For builds on Windows, jq is already included in `/resources/build/`
On Linux
`sudo apt install jq`
### Install Pandoc
Pandoc is used during the build process to generate the app's offline help.
Install from https://pandoc.org/installing.html
### Crash Reporting
Keyman for Android uses [Sentry](https://sentry.io) for crash reporting at a server https://sentry.keyman.com. The analytics for Debug are associated with an App Bundle ID `com.tavultesoft.kmapro.debug`.
#### Setting up sentry-cli
Contact the Keyman team if you need access to sentry.keyman.com for development.
You will also need to install [sentry-cli](https://docs.sentry.io/cli/installation/) for uploading Debug symbols.
After setting up your personal [Auth token](http://sentry.keyman.com/settings/account/api/auth-tokens/), add the following to **~/.bashrc**
```bash
export SENTRY_AUTH_TOKEN={your Sentry auth token}
export SENTRY_URL=https://sentry.keyman.com
export SENTRY_ORG=keyman
export SENTRY_PROJECT=keyman-android
```
To validate your configuration, from the `android/` folder run `sentry-cli info`.
### Compiling From Command Line
1. Launch a command prompt and cd to the directory **keyman/android**

View file

@ -4,6 +4,7 @@ title: What's New
Here are some of the new features we have added to Keyman 15.0 for Android:
* Meet Google Play's target API level requirement of 30 (Android 11)
* Add a settings option to change the displayed keyboard name on the spacebar
[![](../android_images/video.png) Watch a video](https://youtu.be/ehk0Bts28JE)
that highlights some of these new features.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -38,6 +38,20 @@ For installed keyboard packages, click on this to associate another language wit
If all the languages for a keyboard have already been installed,
this won't change anything.
## Change Display Language
![](../../android_images/ic_translate_a.png)
Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale.
## Spacebar Caption
Click on this to change the displayed keyboard name on the spacebar. The options are:
* Language name
* Keyboard name
* Language name + Keyboard name
* Blank
![](../../android_images/spacebar-caption-ap.png)
## Enable Keyman as system-wide keyboard
By default, installed Keyman keyboards are only available to use within the Keyman app. Click on this setting to
bring up the Android system configuration so Keyman can be added as a system-wide keyboard.
@ -45,11 +59,6 @@ bring up the Android system configuration so Keyman can be added as a system-wid
## Set Keyman as default keyboard
Click on this to bring up the Android system configuration for setting Keyman as the default system-wide keyboard.
## Change Display Language
![](../../android_images/ic_translate_a.png)
Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale.
## Always show banner
This toggle is reserved for future features. When off, the text suggestion banner is only displayed when
the dictionary is enabled. The language associated with the keyboard must match the language associated with

View file

@ -1,235 +1,12 @@
# How to build the keyboard processor
## Prerequisites
See [build configuration](../../../../docs/build/index.md) for details on how to configure your build environment.
- Bash (for Windows, included with Git for Windows)
- Python 3
- Meson build system 0.45+ (0.56+ for WASM)
- ninja 1.8+
- C++14 or later compiler (VC++ 2019 or later for Windows).
- lib std::fs
- kmcomp (for tests) -- must be added to path
- Rust 1.50+ from <https://www.rust-lang.org/tools/install> (or `cargo` package on Linux)
For WASM builds:
- Meson build system 0.56+
- emscripten 2.0.23+ <https://emscripten.org/docs/getting_started/downloads.html>
- WasmPack 0.9.1+ <https://github.com/rustwasm/wasm-pack>
- ninja 1.10+ for WASM
### Windows
* Install Git for Windows: <https://gitforwindows.org/>
* Install Python 3:
You can get the official Python installer from the official Python site:
<https://www.python.org/downloads/windows/>
Ensure you have Python3 correctly installed and can run the command `pip`.
Ensure that Python 3 is on the path ahead of Python 2 (if installed).
* Install meson:
```bash
python -m pip install meson
```
* Install [ninja](https://ninja-build.org/) 1.10+:
```bash
choco install ninja
```
* Install Rust:
After downloading and running the executable from:
<https://www.rust-lang.org/tools/install>
Run the command:
```bash
rustup target add i686-pc-windows-msvc
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack:
<https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1/wasm-pack-init.exe>
* Install Emscripten:
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
* Add emcc to PATH (probably upstream\enscripten)
#### Windows environment
On Windows you may need to set `SDKVER` environment variable to the current
Windows SDK version, if it cannot be automatically detected.
```bash
export SDKVER=10.0.19041.0
```
#### kmcomp
Note on paths for kmcomp:
The search path can be edited through System settings / Advanced system settings
/ Environment Variables / User environment variables.
If you have Keyman Developer installed, kmcomp should be on the path already;
otherwise add `%KeymanDeveloperPath%` to your path.
If you do not have Keyman Developer installed, add the path where you extracted
the kmcomp archive.
### Linux
#### Ubuntu and Debian
* Install Python
```bash
sudo apt install python3
```
* Upgrade Meson from version included with Python (WASM builds):
```bash
sudo apt install meson
```
* Install Rust:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack (WASM builds):
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (including adding to path with `emsdk_env.sh`) (WASM builds):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
You may also need the `kmcomp` wrapper - see below.
#### Other Linux distributions
* Install Python 3 and pip
You will be able to install a python3 package in any reputable recent version of
linux using its package manager if it's not already installed. Ensure that `pip`
is also installed.
* Install meson:
```bash
python3 -m pip install meson
```
* Install Rust from <https://www.rust-lang.org/tools/install>, then:
```bash
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack (WASM builds):
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (WASM builds):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
* Add emcc to PATH (probably upstream/enscripten)
You may also need the `kmcomp` wrapper - see below.
#### kmcomp - All Linux platforms
If you want to rebuild keyboards for tests, you need a wrapper `kmcomp` shell
script:
```bash
#!/usr/bin/env bash
wine `dirname "$0"`/kmcomp.exe "$@"
```
Place this in the same folder as you extracted kmcomp.exe, and
`chmod +x kmcomp`. Add the folder to the path (e.g.
`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`).
### macOS
* Install Python 3
You can get the official Python installer from the official Python site:
<https://www.python.org/downloads/mac-osx/>
* Install meson:
```bash
brew install meson # if you haven't already installed via pip
```
* Install Rust:
```bash
curl https://sh.rustup.rs -sSf | sh
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack:
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (including environment update):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
#### kmcomp
If you want to rebuild keyboards for tests, you'll also need WINE:
```bash
brew tap homebrew/cask-versions
brew install --cask --no-quarantine wine-stable
```
And you will also need a wrapper `kmcomp` shell script:
```bash
#!/usr/bin/env bash
wine64 `dirname "$0"`/kmcomp.exe "$@"
```
Place this in the same folder as you extracted kmcomp.exe, and
`chmod +x kmcomp`. Add the folder to the path (e.g.
`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`).
## Building -- all platforms
## Building
On all platforms, use `build.sh`.
* To build native libraries and tests:
```bash
./build.sh --debug
```
* To build WASM libraries and tests:
```bash
./build.sh --platform wasm
```
* See `./build.sh --help` for more details
## Note on kmcomp

View file

@ -29,6 +29,19 @@ void KMX_Context::Add(KMX_WCHAR ch)
//DebugLog("KMX_Context::Add(%x): EXIT [%d]: %s", ch, pos, Debug_UnicodeString(CurContext));
}
KMX_WCHAR *KMX_Context::BufMax(int n)
{
KMX_WCHAR *p = (KMX_WCHAR *) u16chr(CurContext, 0);
if(CurContext == p || n == 0) return p; // empty context or 0 characters requested, return pointer to end of context
KMX_WCHAR *q = p;
for(; p != NULL && p > CurContext && (intptr_t)(q-p) < n; p = decxstr(p, CurContext))
; // ; on new line to tell compiler empty loop is intentional
if((intptr_t)(q-p) > n) p = incxstr(p); // Copes with deadkey or supplementary pair at start of returned buffer making it too long
return p;
}
KMX_WCHAR *KMX_Context::Buf(int n)
{
@ -63,11 +76,17 @@ void KMX_Context::Reset()
void KMX_Context::Get(KMX_WCHAR *buf, int bufsize)
{
for(KMX_WCHAR *p = CurContext; *p && bufsize > 0; p++, bufsize--)
for (KMX_WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--)
{
*buf = *p; buf++;
if(*p >= 0xD800 && *p <= 0xDBFF) { *buf = *(++p); bufsize--; buf++; }
*buf = *p;
if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) {
buf++; p++;
*buf = *p;
bufsize--;
}
buf++;
}
*buf = 0;
}

View file

@ -21,15 +21,89 @@ private:
public:
KMX_Context();
/**
* @brief Copy "source" AppContext to this AppContext
*
* @param source KMX_Context to copy
*/
void CopyFrom(KMX_Context *source);
/**
* @brief Add a single code unit to the Current Context. Not necessarily a complete code point
*
* @param Code unit to add
*/
void Add(KMX_WCHAR ch);
/**
* @brief Removes a single code point from the end of the CurContext closest to the caret;
* i.e. it will be both code units if a surrogate pair. If it is a deadkey it will
* remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value.
*/
void Delete();
/**
* @brief Clears the CurContext and resets the position - pos - index
*/
void Reset();
/**
* @brief Copies the characters in CurContext to supplied buffer.
* If bufsize is reached before the entire context was copied, the buf
* will be truncated to number of valid characters possible with null character
* termination. e.g. it will be one code unit less than bufsize if that would
* have meant splitting a surrogate pair
* @param buf The data buffer to copy current context
* @param bufsize The number of code units ie size of the KMX_WCHAR buffer - not the code points
*/
void Get(KMX_WCHAR *buf, int bufsize);
/**
* @brief Sets the CurContext to the supplied buf character array and updates the pos index.
*
* @param buf
*/
void Set(const KMX_WCHAR *buf);
/**
* @brief Returns a pointer to the character in the current context buffer which
* will have at most n valid xstring units remaining until the null terminating
* character. It will be one code unit less than bufsize if that would
* have meant splitting a surrogate pair or deadkey.
*
* @param n The maximum number of valid xstring units (not code points or code units)
* @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n xstring units
*/
KMX_WCHAR *Buf(int n);
KMX_WCHAR *GetFullContext() { return CurContext; }
/**
* @brief Returns a pointer to the character in the current context buffer which
* will have at most n code units remaining until the the null terminating character.
* Note: Unlike BufMax there is no checking for truncation of code points.
* e.g. the pointer may point to a code unit that is half of a surrogate pair.
*
* @param n
* @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n characters
*/
KMX_WCHAR *BufMax(int n);
/**
* @brief Return a pointer to the CurContext buffer
*
* @return KMX_WCHAR*
*/
const KMX_WCHAR *GetFullContext() { return CurContext; }
/**
* @brief Returns TRUE if the last xstring unit in the context is a deadkey
*
* @return BOOL
*/
KMX_BOOL CharIsDeadkey();
/**
* @brief Returns TRUE if the last xstring unit in the CurContext is a surrogate pair.
* @return BOOL
*/
KMX_BOOL CharIsSurrogatePair();
};

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/input-processor",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/input-processor",
"version": "15.0.74",
"version": "15.0.79",
"description": "The core text and prediction processing engine for KeymanWeb",
"repository": {
"type": "git",
@ -18,8 +18,8 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/lexical-model-compiler": "^15.0.74",
"@keymanapp/resources-gosh": "^15.0.74",
"@keymanapp/lexical-model-compiler": "^15.0.79",
"@keymanapp/resources-gosh": "^15.0.79",
"@types/node": "^11.9.4",
"chai": "^4.3.4",
"mocha": "^8.4.0",
@ -34,11 +34,11 @@
"mocha": "mocha"
},
"dependencies": {
"@keymanapp/keyboard-processor": "^15.0.74",
"@keymanapp/lexical-model-layer": "^15.0.74",
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74",
"@keymanapp/keyboard-processor": "^15.0.79",
"@keymanapp/lexical-model-layer": "^15.0.79",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79",
"eventemitter3": "^4.0.0"
}
}

View file

@ -103,7 +103,9 @@ describe('InputProcessor', function() {
// Assumes no SMP chars in the source, which is fine.
let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength());
this.timeout(250); // 250 ms, excluding text import.
this.timeout(500); // 500 ms, excluding text import.
// These often run on VMs, so we'll be a bit generous.
let core = new InputProcessor(); // I mean, it IS long context, and time
// thresholding is disabled within Node.
@ -137,8 +139,13 @@ describe('InputProcessor', function() {
// Assumes no SMP chars in the source, which is fine.
let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength());
this.timeout(250); // 250 ms, excluding text import.
let core = new InputProcessor(); // I mean, it IS long context, and time
this.timeout(500); // 500 ms, excluding text import.
// These often run on VMs, so we'll be a bit generous.
//
// Keep at the same 'order of magnitude' as the
// 'without fat-fingers' test.
let core = new InputProcessor(); // It IS long context, and time
// thresholding is disabled within Node.
core.activeKeyboard = keyboard;

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/keyboard-processor",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/keyboard-processor",
"version": "15.0.74",
"version": "15.0.79",
"description": "Core module for Keyman keyboard support in KeymanWeb.",
"repository": {
"type": "git",
@ -18,7 +18,7 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/resources-gosh": "^15.0.74",
"@keymanapp/resources-gosh": "^15.0.79",
"chai": "^4.3.4",
"mocha": "^8.4.0",
"mocha-teamcity-reporter": "^4.0.0",
@ -32,9 +32,9 @@
"mocha": "mocha"
},
"dependencies": {
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79",
"@types/node": "^11.9.4"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/recorder-core",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/recorder-core",
"version": "15.0.74",
"version": "15.0.79",
"description": "Core classes used to develop KeymanWeb test cases based on keystrokes",
"main": "index.js",
"scripts": {
@ -17,10 +17,10 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"dependencies": {
"@keymanapp/keyboard-processor": "^15.0.74",
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74"
"@keymanapp/keyboard-processor": "^15.0.79",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79"
},
"devDependencies": {
"typescript": "^3.8.3"

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/web-sentry-manager",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/web-sentry-manager",
"version": "15.0.74",
"version": "15.0.79",
"description": "Common setup for KeymanWeb's Sentry-based error reporting",
"main": "dist/index.js",
"scripts": {
@ -20,7 +20,7 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-environment": "^15.0.79",
"@sentry/browser": "^5.27.4"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/web-utils",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/web-utils",
"version": "15.0.74",
"version": "15.0.79",
"description": "Common utility functions used throughout other Keyman packages",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@ -20,8 +20,8 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/resources-gosh": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/resources-gosh": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@types/node": "^14.0.5",
"typescript": "^3.8.3"
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-templates",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-templates",
"version": "15.0.74",
"version": "15.0.79",
"description": "Backing model implementations (templates) for Keyman's modelling layer",
"keywords": [
"lm",
@ -42,8 +42,8 @@
"url": "https://github.com/keymanapp/keyman/issues"
},
"devDependencies": {
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79",
"@types/chai": "^4.2.11",
"@types/mocha": "^7.0.2",
"@types/node": "^14.0.4",
@ -52,6 +52,6 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@keymanapp/models-wordbreakers": "^15.0.74"
"@keymanapp/models-wordbreakers": "^15.0.79"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-types",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-types",
"version": "15.0.74",
"version": "15.0.79",
"description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.",
"types": "./index.d.ts",
"scripts": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-wordbreakers",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/models-wordbreakers",
"version": "15.0.74",
"version": "15.0.79",
"description": "Word breakers bundled in the modeling layer",
"keywords": [
"lexical model",
@ -40,7 +40,7 @@
"url": "https://github.com/keymanapp/keyman/issues"
},
"devDependencies": {
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/models-types": "^15.0.79",
"@types/chai": "^4.2.11",
"@types/mocha": "^7.0.2",
"chai": "^4.3.4",

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/lexical-model-layer",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/lexical-model-layer",
"version": "15.0.74",
"version": "15.0.79",
"description": "Keyman/Predictive Text integration layer",
"main": "build/index.js",
"scripts": {
@ -21,8 +21,8 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/resources-gosh": "^15.0.74",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/resources-gosh": "^15.0.79",
"@types/node": "^10.17.21",
"chai": "^4.3.4",
"karma": "^6.3.2",
@ -46,10 +46,10 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@keymanapp/models-templates": "^15.0.74",
"@keymanapp/models-wordbreakers": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74",
"@keymanapp/models-templates": "^15.0.79",
"@keymanapp/models-wordbreakers": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79",
"es6-shim": "^0.35.5",
"string.prototype.codepointat": "^0.2.1",
"string.prototype.startswith": "^0.2.0"

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/lexical-model-compiler",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/lexical-model-compiler",
"version": "15.0.74",
"version": "15.0.79",
"description": "Keyman Developer lexical model compiler",
"keywords": [
"keyboard",
@ -37,14 +37,14 @@
"node": ">=12.0.0"
},
"dependencies": {
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/models-types": "^15.0.79",
"commander": "^3.0.0",
"typescript": "^3.8.3",
"xml2js": "^0.4.19"
},
"devDependencies": {
"@keymanapp/models-templates": "^15.0.74",
"@keymanapp/models-wordbreakers": "^15.0.74",
"@keymanapp/models-templates": "^15.0.79",
"@keymanapp/models-wordbreakers": "^15.0.79",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.7",
"@types/node": "^10.14.6",

25
docs/build/index.md vendored Normal file
View file

@ -0,0 +1,25 @@
# How to configure your build environment
Keyman has a wide range of dependencies. We are working to reduce the complexity of setting up a development environment over time, but this document should give you all you need in order to start contributing to Keyman.
## Development Platforms
* [Linux - Ubuntu](linux-ubuntu.md)
* [macOS](macos.md)
* [Windows](windows.md)
## Additional Configuration Documentation
Additional notes are available in the following documents:
* [sentry-cli configuration](sentry-cli.md)
Older notes:
* [Keyman for Android notes](old/android-notes.md)
* [Keyman for iOS notes](old/ios-notes.md)
* [Keyman for macOS notes](old/macos-notes.md)
* [KeymanWeb notes](old/web-notes.md)
* [Keyman Core Desktop notes](old/core-desktop-notes.md)
Many folders may also contain other build instructions in README.md files.

3
docs/build/linux-ubuntu.md vendored Normal file
View file

@ -0,0 +1,3 @@
# Setup your Keyman build environment on Ubuntu
TODO

101
docs/build/macos.md vendored Normal file
View file

@ -0,0 +1,101 @@
# Setup your Keyman build environment on macOS
## Target Projects
On macOS, you can build the following projects:
* Keyman for Android
* Keyman for iOS
* Keyman for macOS
* KeymanWeb
The following libraries can also be built:
* Common/Core/Desktop (macOS, wasm targets)
* Common/Core/Web
The following projects **cannot** be built on macOS:
* Keyman for Linux
* Keyman for Windows
* Keyman Developer
## System Requirements
* Minimum macOS version: macOS Catalina 10.15 or Big Sur 11.0
## Prerequisites
Many dependencies are only required for specific projects.
* XCode (iOS, macOS)
* Install from App Store
* Accept the Xcode license: `sudo xcodebuild -license accept`
The remaining dependencies can be installed via script:
`resources/devbox/macos/macos.sh`
This script will also update your environment to the values in:
`resources/devbox/macos/keyman.macos.env.sh`
It will also add these environment settings to your `~/.bashrc`.
These dependencies are also listed below if you'd prefer to install manually.
## Shared Dependencies
* Shared: HomeBrew, Bash 5.0+, jq, Python 3, Meson, Ninja, Rust, coreutils, Pandoc
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install bash jq python3 meson ninja rustup-init coreutils pandoc rustup-init
```
* Web: node.js, emscripten, wasm-pack, openjdk 8
```shell
brew install node emscripten wasm-pack openjdk@8
rustup target add wasm32-unknown-unknown
```
* iOS: swiftlint, carthage
```shell
brew install swiftlint carthage
```
* macOS: carthage, cocoapods
```shell
brew install carthage cocoapods
```
* Android: openjdk 8, Android SDK, Android Studio, Ant, Gradle, Maven
```shell
brew install openjdk@8 android-sdk android-studio ant gradle maven
# update path
source ../resources/devbox/macos/keyman.macos.env.sh
# optionally install sdk images
sdkmanager "system-images;android-30;google_apis;armeabi-v7a"
sdkmanager --update
sdkmanager --licenses
```
* kmcomp (optional): WINE
- Required to build keyboards using kmcomp
```bash
brew tap homebrew/cask-versions
brew install --cask --no-quarantine wine-stable
```
* sentry-cli (optional)
- Uploading symbols for Sentry-based error reporting
```
brew install getsentry/tools/sentry-cli
```
* Run Android Studio once after installation to install additional components
such as emulator images and SDK updates.

56
docs/build/old/android-notes.md vendored Normal file
View file

@ -0,0 +1,56 @@
# Keyman for Android configuration notes
WARNING: these are old configuration notes. See [index.md](../index.md) for current steps.
* Android Studio 4.1+
* Java SE Development Kit 8
* [Node.js](https://nodejs.org/) 14.17+ (for building KeymanWeb)
* [Pandoc](#Install-Pandoc) for generating offline help
## Install Java
It is recommended to use openJDK because of Oracle license issues.
Tested with latest release for openJDK 8 from
https://github.com/ojdkbuild/ojdkbuild
1. Download and unpack the zip archive
2. On windows: use the default java path C:\Program Files\Java to avoid error message "Error 0x80010135 Path Too Long".
3. Also set an environment variable for JAVA_HOME e.g C:\Program Files\Java\openjdk-1.8.0.232-1
## Setup Android Studio
1. Download [Android Studio](https://developer.android.com/studio/index.html) and install with these [instructions](https://developer.android.com/studio/install.html).
2. For Windows users, set environment variable **ANDROID_HOME** to the location of your Android SDK. The default installation location is **C:\Users\\[USER]\AppData\Local\Android\sdk** where [USER] is your username. You may need to log out and log back in to take effect.
For MacOS/Linux users, add the following to **~/.bashrc** or **~/.bash_profile**
```bash
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/tools
```
For MacOS users, add the following (adjusted appropriately) to **~/.bashrc** or **~/.bash_profile** if your Java version is too strange for gradlew to understand (e.g., 11.0.2)
```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
echo $JAVA_HOME #should output: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
```
3. For Windows users, from a Git Bash Prompt window, cd to the **sdk/tools/bin** folder and accept all the SDK license agreements
```
yes | ./sdkmanager.bat --licenses
```
For MacOS users, from a Terminal window, cd to the **~/Library/Android/sdk/tools/bin** folder and accept all the SDK license agreements
```bash
yes | ./sdkmanager --licenses
```
4. If you plan to test on a physical device via USB, install the appropriate [OEM USB drivers](https://developer.android.com/studio/run/oem-usb.html)
5. Install [Java SE Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
### Install JQ
jq 1.6+ is used during the build process to determine the latest versions of the default keyboard (sil_euro_latin.kmp) and lexical-model (en.nrc.mtnt.model.kmp) packages to download. For builds on Windows, jq is already included in `/resources/build/`
On Linux
`sudo apt install jq`
### Install Pandoc
Pandoc is used during the build process to generate the app's offline help.
Install from https://pandoc.org/installing.html

231
docs/build/old/core-desktop-notes.md vendored Normal file
View file

@ -0,0 +1,231 @@
# Common/Core/Desktop configuration notes
WARNING: these are old configuration notes. See [index.md](../index.md) for current steps.
- Bash (for Windows, included with Git for Windows)
- Python 3
- Meson build system 0.45+ (0.56+ for WASM)
- ninja 1.8+
- C++14 or later compiler (VC++ 2019 or later for Windows).
- lib std::fs
- kmcomp (for tests) -- must be added to path
- Rust 1.50+ from <https://www.rust-lang.org/tools/install> (or `cargo` package on Linux)
For WASM builds:
- Meson build system 0.56+
- emscripten 2.0.23+ <https://emscripten.org/docs/getting_started/downloads.html>
- WasmPack 0.9.1+ <https://github.com/rustwasm/wasm-pack>
- ninja 1.10+ for WASM
### Windows
* Install Git for Windows: <https://gitforwindows.org/>
* Install Python 3:
You can get the official Python installer from the official Python site:
<https://www.python.org/downloads/windows/>
Ensure you have Python3 correctly installed and can run the command `pip`.
Ensure that Python 3 is on the path ahead of Python 2 (if installed).
* Install meson:
```bash
python -m pip install meson
```
* Install [ninja](https://ninja-build.org/) 1.10+:
```bash
choco install ninja
```
* Install Rust:
After downloading and running the executable from:
<https://www.rust-lang.org/tools/install>
Run the command:
```bash
rustup target add i686-pc-windows-msvc
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack:
<https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1/wasm-pack-init.exe>
* Install Emscripten:
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
* Add emcc to PATH (probably upstream\enscripten)
#### Windows environment
On Windows you may need to set `SDKVER` environment variable to the current
Windows SDK version, if it cannot be automatically detected.
```bash
export SDKVER=10.0.19041.0
```
#### kmcomp
Note on paths for kmcomp:
The search path can be edited through System settings / Advanced system settings
/ Environment Variables / User environment variables.
If you have Keyman Developer installed, kmcomp should be on the path already;
otherwise add `%KeymanDeveloperPath%` to your path.
If you do not have Keyman Developer installed, add the path where you extracted
the kmcomp archive.
### Linux
#### Ubuntu and Debian
* Install Python
```bash
sudo apt install python3
```
* Upgrade Meson from version included with Python (WASM builds):
```bash
sudo apt install meson
```
* Install Rust:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack (WASM builds):
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (including adding to path with `emsdk_env.sh`) (WASM builds):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
You may also need the `kmcomp` wrapper - see below.
#### Other Linux distributions
* Install Python 3 and pip
You will be able to install a python3 package in any reputable recent version of
linux using its package manager if it's not already installed. Ensure that `pip`
is also installed.
* Install meson:
```bash
python3 -m pip install meson
```
* Install Rust from <https://www.rust-lang.org/tools/install>, then:
```bash
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack (WASM builds):
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (WASM builds):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
* Add emcc to PATH (probably upstream/enscripten)
You may also need the `kmcomp` wrapper - see below.
#### kmcomp - All Linux platforms
If you want to rebuild keyboards for tests, you need a wrapper `kmcomp` shell
script:
```bash
#!/usr/bin/env bash
wine `dirname "$0"`/kmcomp.exe "$@"
```
Place this in the same folder as you extracted kmcomp.exe, and
`chmod +x kmcomp`. Add the folder to the path (e.g.
`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`).
### macOS
* Install Python 3
You can get the official Python installer from the official Python site:
<https://www.python.org/downloads/mac-osx/>
* Install meson:
```bash
brew install meson # if you haven't already installed via pip
```
* Install Rust:
```bash
curl https://sh.rustup.rs -sSf | sh
rustup target add wasm32-unknown-unknown
```
* Install wasm-pack:
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
* Install Enscripten (including environment update):
<https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install>
#### kmcomp
If you want to rebuild keyboards for tests, you'll also need WINE:
```bash
brew tap homebrew/cask-versions
brew install --cask --no-quarantine wine-stable
```
And you will also need a wrapper `kmcomp` shell script:
```bash
#!/usr/bin/env bash
wine64 `dirname "$0"`/kmcomp.exe "$@"
```
Place this in the same folder as you extracted kmcomp.exe, and
`chmod +x kmcomp`. Add the folder to the path (e.g.
`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`).
## Building -- all platforms
On all platforms, use `build.sh`.
* To build native libraries and tests:
```bash
./build.sh --debug
```
* To build WASM libraries and tests:
```bash
./build.sh --platform wasm
```

21
docs/build/old/ios-notes.md vendored Normal file
View file

@ -0,0 +1,21 @@
# Keyman for iOS configuration notes
WARNING: these are old configuration notes. See [macos.md](../macos.md) for current steps.
## Prerequisites
* Xcode 11
* iOS 9+
* SwiftLint (`brew install swiftlint`)
* Carthage 0.37+ (`brew install carthage`)
* Pandoc (`brew install pandoc`)
- Used solely to generate offline help.
* Coreutils (`brew install coreutils`)
* sentry-cli (`brew install getsentry/tools/sentry-cli`) to utilize Sentry-based error reporting
* jq (`brew install jq`)
* `bash` (`brew install bash`)
* Building Keyman Web is a precursor for compiling KMEI, so verify your system has all the [Minimum Web Compilation Requirements](../web/README.md#minimum-web-compilation-requirements), including (but not limited to):
- [Node.js](https://nodejs.org/) 8.9+ (for building the embedded KeymanWeb engine)
- [Java 7+](https://adoptopenjdk.net/releases.html)
Note that the `brew` command mentioned above is Homebrew, which may be found at https://brew.sh/.
While not strictly necessary, it certainly simplifies installing the prerequisites above.

17
docs/build/old/macos-notes.md vendored Normal file
View file

@ -0,0 +1,17 @@
# Keyman for macOS configuration notes
WARNING: these are old configuration notes. See [macos.md](../macos.md) for current steps.
## Mac Tools Requirements/Setup
* Install [Homebrew] *technically optional, but highly recommended*
* Install Xcode 12.0+
* Accept the Xcode license `sudo xcodebuild -license accept`
* Install [Carthage] (`brew install carthage`) *see Homebrew note below*
* Install [cocoapods] (`brew install cocoapods`)
* Install [coreutils] (`brew install coreutils`)
[Homebrew]: https://brew.sh/
[Carthage]: https://github.com/Carthage/Carthage/blob/master/README.md
[cocoapods]: https://cocoapods.org/
[coreutils]: https://www.gnu.org/software/coreutils/

18
docs/build/old/web-notes.md vendored Normal file
View file

@ -0,0 +1,18 @@
# KeymanWeb configuration notes
WARNING: these are old configuration notes. See [index.md](../index.md) for current steps.
## Minimum Web Compilation Requirements
* [Java 7+](https://adoptopenjdk.net/releases.html) (required by the Google Closure Compiler)
* This can be ignored for Keyman Developer builds.
* A local installation of [Node.js](https://nodejs.org/) v8.9+.
* Builds will call `npm install` to automatically install further necessary build dependencies.
* Linux users can run the following to update to LTS version of nodejs
```
sudo apt-get install python-software-properties
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install nodejs
```

15
docs/build/sentry-cli.md vendored Normal file
View file

@ -0,0 +1,15 @@
# Setting up sentry-cli
Contact the Keyman team if you need access to sentry.keyman.com for development.
You will also need to install [sentry-cli](https://docs.sentry.io/cli/installation/) for uploading Debug symbols.
After setting up your personal [Auth token](http://sentry.keyman.com/settings/account/api/auth-tokens/), add the following to **~/.bashrc**
```bash
export SENTRY_AUTH_TOKEN={your Sentry auth token}
export SENTRY_URL=https://sentry.keyman.com
export SENTRY_ORG=keyman
# select the appropriate project here:
export SENTRY_PROJECT=keyman-android
```
To validate your configuration, for example for keyman-android, from the `android/` folder run `sentry-cli info`.

3
docs/build/windows.md vendored Normal file
View file

@ -0,0 +1,3 @@
# Setup your Keyman build environment on Windows
TODO

View file

@ -1,25 +1,9 @@
# Keyman for iOS
## Prerequisites
* Xcode 11
* iOS 9+
* SwiftLint (`brew install swiftlint`)
* Carthage 0.37+ (`brew install carthage`)
* Pandoc (`brew install pandoc`)
- Used solely to generate offline help.
* Coreutils (`brew install coreutils`)
* sentry-cli (`brew install getsentry/tools/sentry-cli`) to utilize Sentry-based error reporting
* jq (`brew install jq`)
* `bash` (`brew install bash`)
* Building Keyman Web is a precursor for compiling KMEI, so verify your system has all the [Minimum Web Compilation Requirements](../web/README.md#minimum-web-compilation-requirements), including (but not limited to):
- [Node.js](https://nodejs.org/) 8.9+ (for building the embedded KeymanWeb engine)
- [Java 7+](https://adoptopenjdk.net/releases.html)
Note that the `brew` command mentioned above is Homebrew, which may be found at https://brew.sh/.
While not strictly necessary, it certainly simplifies installing the prerequisites above.
See [build configuration](../docs/build/index.md) for details on how to configure your build environment.
## Keyman App
The source for our published iOS Keyman app is found within the **keyman/** subdirectory. This project only contains
the views specific to the Keyman app, while the core functionality is found in KeymanEngine.framework.
@ -94,11 +78,3 @@ The KeymanEngine project contains a demo app that demonstrates usage of the fram
create a system keyboard.
To build the samples, `cd` into the project directory and run `./build.sh`.
### Compiling the app's offline help
Extra prerequisite:
* `wget` (`brew install wget`)
The script `build-help.sh` uses the `wget` tool to construct an offline bundle from the current
online version of help on help.keyman.com. When significant changes to help content have been
made, it is advisable to manually re-run this script to update the app's offline content.

View file

@ -20,5 +20,5 @@
"ts-node",
"typescript"
],
"version": "15.0.74"
"version": "15.0.79"
}

View file

@ -14,7 +14,7 @@ DEBIAN_SUITES=($UNSTABLE_CODENAME $TESTING_CODENAME $STABLE_CODENAME $STABLE_BAC
"experimental" "unstable" "testing" "stable")
# List of Ubuntu suites. Update these when needed.
UBUNTU_SUITES=("hirsute" "groovy" "focal" "bionic")
UBUNTU_SUITES=("impish" "hirsute" "groovy" "focal" "bionic")
# Mirrors to use. Update these to your preferred mirror.
DEBIAN_MIRROR="deb.debian.org"

2
linux/Jenkinsfile vendored
View file

@ -5,6 +5,6 @@
@Library('lsdev-pipeline-library') _
keymanPackaging {
distributionsToPackage = 'bionic focal groovy hirsute'
distributionsToPackage = 'bionic focal groovy hirsute impish'
arches = 'amd64 i386'
}

View file

@ -5,4 +5,5 @@ title: Version History
A frequent updated changelog for Keyman is kept at [help.keyman.com/version-history](https://help.keyman.com/version-history) on the Keyman Help website.
## Related Topics
* [What's New](whatsnew)

View file

@ -4,4 +4,4 @@ title: About Keyman
* [Welcome to Keyman](welcome)
* [What's New](whatsnew)
* [Version History](history)
* [Version History](history)

View file

@ -5,8 +5,9 @@ title: Welcome to Keyman
Thank you for installing Keyman for Linux. Whether you're a new or returning user, we believe you will appreciate the significant enhancements in this latest version of Keyman for Linux.
## What is Keyman for Linux?
Keyman for Linux makes it possible to type in over 1,000 languages in any Linux application.
Keyman for Linux also supports KMFL keyboards, so your existing KMFL keyboards will continue to work.
[Click here](../start/) to get started.
[Click here](../start/) to get started.

View file

@ -9,5 +9,5 @@ Here are some of the new features we have added to Keyman for Linux 14.0:
* Improved user interface
* Improved support for KDE, Gnome, Arch Linux
[![](../linux_images/video.png) Watch a video](https://youtu.be/4W9Z9_IFUII)
[![Watch a video](../linux_images/video.png)](https://youtu.be/4W9Z9_IFUII)
that highlights some of these new features.

View file

@ -3,7 +3,9 @@ title: Common Questions
---
## Q. How do I install Keyman for Linux?
**A.** In Ubuntu and Wasta-Linux, Keyman for Linux can be installed via launchpad:
```bash
sudo add-apt-repository ppa:keymanapp/keyman
sudo apt-get update
@ -11,22 +13,25 @@ sudo apt-get upgrade
sudo apt-get install keyman onboard-keyman
```
An older version of Keyman for Linux packages is also available in any current Debian at:
An older version of Keyman for Linux packages is also available in any current Debian at:
[https://packages.debian.org/keyman](https://packages.debian.org/keyman)
## Q. How do I install a Keyman keyboard?
**A.** Follow this [guide](../start/installing-keyboard) for installing Keyman keyboards.
## Q. What's the relationship between Keyman for Linux and KMFL?
**A.** Keyman for Linux and KMFL are released together in the Keyman
[PPA](https://en.wikipedia.org/wiki/Ubuntu#Package_Archives), but are separate. The installation
instructions above will install Keyman, not KMFL. KMFL was developed as a black-box clone of Keyman
for Windows years ago; after Keyman was released as open source it became possible to share the
for Windows years ago; after Keyman was released as open source it became possible to share the
code base between Windows and Linux. Keyman for Linux is the long-term supported solution.
## Q. Can I have Keyman for Linux and KMFL installed at the same time?
**A.** Yes. To install KMFL on Ubuntu:
```bash
sudo add-apt-repository ppa:keymanapp/keyman
sudo apt-get update
@ -35,22 +40,27 @@ sudo apt-get install ibus-kmfl
```
## Q. If I already had KMFL installed, how can I uninstall KMFL before installing Keyman?
**A.** It is good to remove any keyboards from ibus e.g. KMFL keyboards before you remove KMFL.
Then, to remove KMFL:
```bash
sudo dpkg --purge ibus-kmfl libkmfl
```
## Q. How do I display the On-screen Keyboard (OSK)?
**A.** From the launcher, enter `onboard`
![](../linux_images/onboard.png "Onboard")
## Q. What Linux distros will Keyman work with?
**A.** Keyman runs on Debian, Ubuntu, Wasta Linux and can be compiled to run from source in most distributions.
**Note:** Keyman for Linux no longer supports Ubuntu 16.04 LTS (Xenial Xerus).
## Q. Will my existing Windows Keyman keyboard work with Keyman for Linux?
**A.** Most keyboards will work without change. A small subset of keyboards require features which
are not yet available in Keyman for Linux. These features will be progressively implemented.

View file

@ -1,20 +1,23 @@
---
title: Keyman for Linux 14.0 Help
title: Keyman for Linux 15.0 Help
---
Need help using Keyman for Linux? In time, this product documentation will grow and explain frequently asked questions.
## [About Keyman for Linux](about/)
* [Welcome to Keyman](about/welcome)
* [What's New](about/whatsnew)
* [Version History](about/history)
## [Getting Started](start/)
* [Installing a keyboard](start/installing-keyboard)
## [Common Questions](common/)
## Ask for Help
[Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman)
## Help Topics

View file

@ -0,0 +1,25 @@
---
title: Keyman 15.0 for Linux Reference
---
## Keyman 15.0 for Linux Overview
Keyman for Linux makes it possible for you to type in over 1,000 languages on Linux.
[km-config](km-config) \
Keyman keyboards installation and information
[km-kvk2ldml](km-kvk2ldml) \
Convert a Keyman on-screen keyboard file to LDML
[km-package-get](km-package-get) \
Download a Keyman keyboard package
[km-package-install](km-package-install) \
Install a Keyman keyboard package
[km-package-list-installed](km-package-list-installed) \
List installed Keyman keyboard packages
[km-package-uninstall](km-package-uninstall) \
Uninstall a Keyman keyboard package

View file

@ -1,5 +1,5 @@
---
title: Getting Started"
title: Getting Started
---
* [How To - Installing Keyboards with Keyman for Linux](installing-keyboard)

View file

@ -8,6 +8,7 @@ Alternatively from the command line run `km-config`.
![](../linux_images/km-config.png "Keyman Configuration")
## Download from Keyman cloud repository
From Keyman Configuration, click "Download". At the search page, enter the keyboard you want to download.
![](../linux_images/search-khmer-angkor.png "Search for khmer_angkor keyboard")
@ -17,6 +18,7 @@ Clicking the keyboard brings the keyboard page which has a link for installation
![](../linux_images/download-khmer-angkor.png "Install khmer_angkor")
## Install Local .kmp keyboard package
Keyman for Linux can also install keyboards from local .kmp keyboard packages. From Keyman Configuration,
click "Install". Then browse to the local .kmp keyboard package and install.

View file

@ -1,3 +1,10 @@
keyman-config (14.0.276-2) experimental; urgency=medium
* Team upload
* Add libglib2.0-bin to keyman Depends (closes: #990226)
-- Gunnar Hjalmarsson <gunnarhj@debian.org> Wed, 23 Jun 2021 15:28:27 +0200
keyman-config (14.0.276-1) experimental; urgency=medium
* New upstream release

View file

@ -34,6 +34,7 @@ Homepage: https://www.keyman.com
Package: keyman
Architecture: all
Depends:
libglib2.0-bin,
python3-keyman-config,
${misc:Depends},
Recommends:

View file

@ -1,3 +1,4 @@
km-config.bash-completion km-config
km-kvk2ldml.bash-completion km-kvk2ldml
km-package-get.bash-completion km-package-get
km-package-install.bash-completion km-package-install

View file

@ -0,0 +1,25 @@
#!/usr/bin/python3
# based on https://stackoverflow.com/a/40301488
import logging
string_types = (type(b''), type(u''))
def deprecated(reason):
if isinstance(reason, string_types):
# The @deprecated is used with a 'reason'.
def decorator(func1):
def new_func1(*args, **kwargs):
logging.warning("Call to deprecated function '{name}': {reason}.".format(
name=func1.__name__, reason=reason))
return func1(*args, **kwargs)
return new_func1
return decorator
else:
# The @deprecated is used without any 'reason'.
def new_func2(*args, **kwargs):
func2 = reason
logging.warning("Call to deprecated function '{name}'.".format(name=func2.__name__))
return func2(*args, **kwargs)
return new_func2

View file

@ -6,7 +6,16 @@ import logging
import requests
import requests_cache
import os
from gi.repository import GObject
from keyman_config import KeymanApiUrl, KeymanDownloadsUrl
from keyman_config.deprecated_decorator import deprecated
class InstallLocation(GObject.GEnum):
OS = 1
Shared = 2
User = 3
Unknown = 99
def get_package_download_data(packageID, weekCache=False):
@ -19,9 +28,9 @@ def get_package_download_data(packageID, weekCache=False):
Returns:
dict: Keyboard data
"""
logging.info("Getting download data for package %s", packageID)
api_url = KeymanDownloadsUrl + "/api/keyboard/1.0/" + packageID
logging.debug("At URL %s", api_url)
logging.info('Getting download data for package %s', packageID)
api_url = KeymanDownloadsUrl + '/api/keyboard/1.0/' + packageID
logging.debug('At URL %s', api_url)
cache_dir = keyman_cache_dir()
current_dir = os.getcwd()
if weekCache:
@ -32,7 +41,7 @@ def get_package_download_data(packageID, weekCache=False):
requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after)
now = time.ctime(int(time.time()))
response = requests.get(api_url)
logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache))
logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache))
os.chdir(current_dir)
requests_cache.core.uninstall_cache()
if response.status_code == 200:
@ -51,9 +60,9 @@ def get_keyboard_data(keyboardID, weekCache=False):
Returns:
dict: Keyboard data
"""
logging.info("Getting data for keyboard %s", keyboardID)
api_url = KeymanApiUrl + "/keyboard/" + keyboardID
logging.debug("At URL %s", api_url)
logging.info('Getting data for keyboard %s', keyboardID)
api_url = KeymanApiUrl + '/keyboard/' + keyboardID
logging.debug('At URL %s', api_url)
cache_dir = keyman_cache_dir()
current_dir = os.getcwd()
if weekCache:
@ -64,7 +73,7 @@ def get_keyboard_data(keyboardID, weekCache=False):
requests_cache.install_cache(cache_name='keyman_cache', backend='sqlite', expire_after=expire_after)
now = time.ctime(int(time.time()))
response = requests.get(api_url)
logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache))
logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache))
os.chdir(current_dir)
requests_cache.core.uninstall_cache()
if response.status_code == 200:
@ -91,28 +100,54 @@ def keyman_cache_dir():
Returns:
str: path of user keyman cache folder
"""
home = os.path.expanduser("~")
cachebase = os.environ.get("XDG_CACHE_HOME", os.path.join(home, ".cache"))
km_cache = os.path.join(cachebase, "keyman")
home = os.path.expanduser('~')
cachebase = os.environ.get('XDG_CACHE_HOME', os.path.join(home, '.cache'))
km_cache = os.path.join(cachebase, 'keyman')
if not os.path.isdir(km_cache):
os.mkdir(km_cache)
return km_cache
def _get_data_base_dir(area):
if area == InstallLocation.User:
return os.environ.get('XDG_DATA_HOME',
os.path.join(os.path.expanduser('~'), '.local', 'share'))
if area == InstallLocation.Shared:
return '/usr/local/share'
return '/usr/share'
@deprecated('Use get_keyman_dir(InstallLocation.User) instead')
def user_keyman_dir():
home = os.path.expanduser("~")
datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share"))
return os.path.join(datahome, "keyman")
return get_keyman_dir(InstallLocation.User)
def get_keyman_dir(area):
return os.path.join(_get_data_base_dir(area), 'keyman')
@deprecated('Use get_keyman_font_dir(InstallLocation.User) instead')
def user_keyman_font_dir():
home = os.path.expanduser("~")
datahome = os.environ.get("XDG_DATA_HOME", os.path.join(home, ".local", "share"))
return os.path.join(datahome, "fonts", "keyman")
return os.path.join(_get_data_base_dir(InstallLocation.User), 'fonts', 'keyman')
def get_keyman_font_dir(area, keyboardid):
return os.path.join(_get_data_base_dir(area), 'fonts', 'keyman', keyboardid)
@deprecated('Use get_keyboard_dir(InstallLocation.User, keyboardid) instead')
def user_keyboard_dir(keyboardid):
return os.path.join(user_keyman_dir(), keyboardid)
return get_keyboard_dir(InstallLocation.User, keyboardid)
def get_keyboard_dir(area, keyboardid):
return os.path.join(_get_data_base_dir(area), 'keyman', keyboardid)
def get_keyman_doc_dir(area, keyboardid):
if area == InstallLocation.User:
return get_keyboard_dir(area, keyboardid)
return os.path.join(_get_data_base_dir(area), 'doc', 'keyman')
def get_kmp_file(downloaddata, cache=False):
@ -126,7 +161,7 @@ def get_kmp_file(downloaddata, cache=False):
str: path where kmp file has been downloaded
"""
if 'kmp' not in downloaddata:
logging.info("get_kmp.py: Package does not have a kmp file available")
logging.info('get_kmp.py: Package does not have a kmp file available')
return None
downloadfile = os.path.join(get_download_folder(), os.path.basename(downloaddata['kmp']))
@ -146,7 +181,7 @@ def download_kmp_file(url, kmpfile, cache=False):
Returns:
str: path where kmp file has been downloaded
"""
logging.info("Download URL: %s", url)
logging.info('Download URL: %s', url)
downloadfile = None
if cache:
@ -162,11 +197,11 @@ def download_kmp_file(url, kmpfile, cache=False):
try:
response = requests.get(url) # , stream=True)
except requests.exceptions.ConnectionError:
logging.error("Connection error downloading %s", url)
logging.error('Connection error downloading %s', url)
return downloadfile
if cache:
logging.debug("Time: {0} / Used Cache: {1}".format(now, response.from_cache))
logging.debug('Time: {0} / Used Cache: {1}'.format(now, response.from_cache))
os.chdir(current_dir)
requests_cache.core.uninstall_cache()
@ -190,6 +225,6 @@ def get_kmp(packageID):
if (downloaddata):
return get_kmp_file(downloaddata)
else:
logging.warning("get_kmp.py: Could not get download information about keyboard package.")
logging.warning('get_kmp.py: Could not get download information about keyboard package.')
return None
return

View file

@ -1,5 +1,6 @@
#!/usr/bin/python3
import os
import time
import gi
import logging
@ -68,15 +69,21 @@ def restart_ibus_subp():
def restart_ibus(bus=None):
try:
if not bus:
bus = get_ibus_bus()
logging.info("restarting IBus")
bus.exit(True)
bus.destroy()
except Exception as e:
logging.warning("Failed to restart IBus")
logging.warning(e)
realuser = os.environ.get('SUDO_USER')
if realuser:
# we have been called with `sudo`. Restart ibus for the real user.
logging.info('restarting IBus by subprocess for user %s', realuser)
subprocess.run(['sudo', '-u', realuser, 'ibus', 'restart'])
else:
try:
if not bus:
bus = get_ibus_bus()
logging.info("restarting IBus")
bus.exit(True)
bus.destroy()
except Exception as e:
logging.warning("Failed to restart IBus")
logging.warning(e)
def bus_has_engine(bus, name):

View file

@ -10,7 +10,8 @@ from enum import Enum
from keyman_config import _
from keyman_config.canonical_language_code_utils import CanonicalLanguageCodeUtils
from keyman_config.fcitx_util import is_fcitx_running, restart_fcitx
from keyman_config.get_kmp import get_keyboard_data, user_keyboard_dir, user_keyman_font_dir
from keyman_config.get_kmp import get_keyboard_data, get_keyboard_dir, get_keyman_doc_dir
from keyman_config.get_kmp import get_keyman_font_dir, InstallLocation
from keyman_config.kmpmetadata import get_metadata, KMFileTypes
from keyman_config.convertico import extractico, checkandsaveico
from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml
@ -51,7 +52,7 @@ class InstallKmp():
self.kmpdocdir = ''
self.kmpfontdir = ''
def _check_keyman_dir(basedir, error_message):
def _check_keyman_dir(self, basedir, error_message):
# check if keyman subdir exists
keyman_dir = os.path.join(basedir, "keyman")
if os.path.isdir(keyman_dir):
@ -89,17 +90,17 @@ class InstallKmp():
_("You do not have permissions to install the font files to the shared font area "
"/usr/local/share/fonts"))
return self._install_kmp(inputfile, online, language)
return self._install_kmp(inputfile, online, language, InstallLocation.Shared)
def install_kmp_user(self, inputfile, online=False, language=None):
return self._install_kmp(inputfile, online, language, InstallLocation.User)
def _install_kmp(self, inputfile, online, language, area):
self.packageID = self._extract_package_id(inputfile)
self.packageDir = user_keyboard_dir(self.packageID)
self.kmpdocdir = self.packageDir
self.kmpfontdir = os.path.join(user_keyman_font_dir(), self.packageID)
self.packageDir = get_keyboard_dir(area, self.packageID)
self.kmpdocdir = get_keyman_doc_dir(area, self.packageID)
self.kmpfontdir = get_keyman_font_dir(area, self.packageID)
return self._install_kmp(inputfile, online, language)
def _install_kmp(self, inputfile, online, language):
if not os.path.isdir(self.packageDir):
os.makedirs(self.packageDir)
@ -228,7 +229,9 @@ class InstallKmp():
# install all kmx for first lang not just packageID
for kb in keyboards:
ibus_keyboard_id = get_ibus_keyboard_id(kb, packageDir, language)
sources.append(('ibus', ibus_keyboard_id))
input_source = ('ibus', ibus_keyboard_id)
if input_source not in sources:
sources.append(input_source)
gnomeKeyboardsUtil.write_input_sources(sources)
return ''

View file

@ -11,26 +11,26 @@ import webbrowser
import tempfile
import gi
from keyman_config.fcitx_util import is_fcitx_running
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gtk, WebKit2
from distutils.version import StrictVersion
from keyman_config import _
from keyman_config.fcitx_util import is_fcitx_running
from keyman_config.install_kmp import install_kmp, extract_kmp, get_metadata, InstallError, InstallStatus
from keyman_config.list_installed_kmp import get_kmp_version
from keyman_config.kmpmetadata import get_fonts
from keyman_config.welcome import WelcomeView
from keyman_config.uninstall_kmp import uninstall_kmp
from keyman_config.get_kmp import user_keyboard_dir
from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir
from keyman_config.accelerators import bind_accelerator, init_accel
def find_keyman_image(image_file):
img_path = os.path.join("/usr/share/keyman/icons", image_file)
img_path = os.path.join(get_keyman_dir(InstallLocation.OS), "icons", image_file)
if not os.path.isfile(img_path):
img_path = os.path.join("/usr/local/share/keyman/icons/", image_file)
img_path = os.path.join(get_keyman_dir(InstallLocation.Shared), "icons", image_file)
if not os.path.isfile(img_path):
img_path = os.path.join("keyman_config/icons/", image_file)
if not os.path.isfile(img_path):
@ -302,7 +302,7 @@ class InstallKmpWindow(Gtk.Dialog):
result = install_kmp(self.kmpfile, self.online, language=self.language)
if result:
# If install_kmp returns a string, it is an instruction for the end user,
# because for fcitx they will need to take extra steps to complete
# because for fcitx they will need to take extra steps to complete
# installation themselves.
dialog = Gtk.MessageDialog(
self, 0, Gtk.MessageType.INFO,
@ -315,7 +315,8 @@ class InstallKmpWindow(Gtk.Dialog):
if self.viewwindow:
self.viewwindow.refresh_installed_kmp()
keyboardid = os.path.basename(os.path.splitext(self.kmpfile)[0])
welcome_file = os.path.join(user_keyboard_dir(keyboardid), "welcome.htm")
welcome_file = os.path.join(get_keyboard_dir(InstallLocation.User, keyboardid),
"welcome.htm")
if os.path.isfile(welcome_file):
uri_path = pathlib.Path(welcome_file).as_uri()
logging.debug(uri_path)

View file

@ -3,8 +3,9 @@
# Keyboard details window
import json
import os.path
from os import path
import qrcode
import tempfile
import gi
from gi.repository import Gtk
@ -40,8 +41,8 @@ class KeyboardDetailsView(Gtk.Dialog):
self.set_border_width(6)
packageDir = os.path.join(kmp['areapath'], kmp['packageID'])
kmp_json = os.path.join(packageDir, "kmp.json")
packageDir = path.join(kmp['areapath'], kmp['packageID'])
kmp_json = path.join(packageDir, "kmp.json")
info, system, options, keyboards, files = parsemetadata(kmp_json)
if info is None:
@ -58,15 +59,15 @@ class KeyboardDetailsView(Gtk.Dialog):
return
kbdata = None
jsonfile = os.path.join(packageDir, kmp['packageID'] + ".json")
if os.path.isfile(jsonfile):
jsonfile = path.join(packageDir, kmp['packageID'] + ".json")
if path.isfile(jsonfile):
with open(jsonfile, "r") as read_file:
kbdata = json.load(read_file)
grid = Gtk.Grid()
# grid.set_column_homogeneous(True)
# kbdatapath = os.path.join("/usr/local/share/keyman", kmp["id"], kmp["id"] + ".json")
# kbdatapath = path.join("/usr/local/share/keyman", kmp["id"], kmp["id"] + ".json")
# Package info
@ -162,8 +163,8 @@ class KeyboardDetailsView(Gtk.Dialog):
if keyboards:
for kbd in keyboards:
kbdata = None
jsonfile = os.path.join(packageDir, kbd['id'] + ".json")
if os.path.isfile(jsonfile):
jsonfile = path.join(packageDir, kbd['id'] + ".json")
if path.isfile(jsonfile):
with open(jsonfile, "r") as read_file:
kbdata = json.load(read_file)
@ -182,7 +183,7 @@ class KeyboardDetailsView(Gtk.Dialog):
grid.attach_next_to(lbl_kbd_file, prevlabel, Gtk.PositionType.BOTTOM, 1, 1)
prevlabel = lbl_kbd_file
label = Gtk.Label()
label.set_text(os.path.join(packageDir, kbd['id'] + ".kmx"))
label.set_text(path.join(packageDir, kbd['id'] + ".kmx"))
label.set_halign(Gtk.Align.START)
label.set_selectable(True)
grid.attach_next_to(label, lbl_kbd_file, Gtk.PositionType.RIGHT, 1, 1)
@ -291,8 +292,8 @@ class KeyboardDetailsView(Gtk.Dialog):
# prevlabel = label8
# #TODO need to know which area keyboard is installed in to show this
# # label = Gtk.Label()
# # welcome_file = os.path.join("/usr/local/share/doc/keyman", kmp["id"], "welcome.htm")
# # if os.path.isfile(welcome_file):
# # welcome_file = path.join("/usr/local/share/doc/keyman", kmp["id"], "welcome.htm")
# # if path.isfile(welcome_file):
# # label.set_text(_("Installed"))
# # else:
# # label.set_text(_("Not installed"))
@ -323,9 +324,9 @@ class KeyboardDetailsView(Gtk.Dialog):
prevlabel = lbl_pad
# If it doesn't exist, generate QR code to share keyboard package
path_qr = packageDir + "/qrcode.png"
path_qr = path.join(tempfile.gettempdir(), kmp['packageID'] + '_qrcode.png')
url = KeymanComUrl + "/go/keyboard/" + kmp['packageID'] + "/share"
if not os.path.isfile(path_qr):
if not path.isfile(path_qr):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,

View file

@ -2,41 +2,14 @@
import os
import json
from gi.repository import GObject
from keyman_config.kmpmetadata import parsemetadata, parseinfdata
from keyman_config.get_kmp import user_keyman_dir
class InstallArea(GObject.GEnum):
IA_OS = 1
IA_SHARED = 2
IA_USER = 3
IA_UNKNOWN = 99
from keyman_config.get_kmp import get_keyman_dir, InstallLocation
from keyman_config.deprecated_decorator import deprecated
@deprecated('Use get_keyman_dir(area) instead')
def get_install_area_path(area):
"""
Get the path of an install area.
Args:
area (InstallArea): install area to check
InstallArea.IA_USER: ~/.local/share/keyman
InstallArea.IA_SHARED: /usr/local/share/keyman
InstallArea.IA_OS: /usr/share/keyman
InstallArea.IA_UNKNOWN: /usr/share/keyman
Returns:
string: path of the install area
"""
check_path = "/usr/share/keyman"
if area == InstallArea.IA_USER:
check_path = user_keyman_dir()
elif area == InstallArea.IA_SHARED:
check_path = "/usr/local/share/keyman"
elif area == InstallArea.IA_OS:
check_path = "/usr/share/keyman"
return check_path
return get_keyman_dir(area)
def get_installed_kmp(area):
@ -44,10 +17,10 @@ def get_installed_kmp(area):
Get list of installed kmp in an install area.
Args:
area (InstallArea): install area to check
InstallArea.IA_USER: ~/.local/share/keyman
InstallArea.IA_SHARED: /usr/local/share/keyman
InstallArea.IA_OS: /usr/share/keyman
area (InstallLocation): install area to check
InstallLocation.User: ~/.local/share/keyman
InstallLocation.Shared: /usr/local/share/keyman
InstallLocation.OS: /usr/share/keyman
Returns:
list: Installed kmp
dict: Keyboard
@ -59,7 +32,7 @@ def get_installed_kmp(area):
path (str): base path where keyboard is installed
description (str): Keyboard description
"""
check_paths = [get_install_area_path(area)]
check_paths = [get_keyman_dir(area)]
return get_installed_kmp_paths(check_paths)
@ -143,9 +116,9 @@ def get_kmp_version(packageID):
None: if not found
"""
version = None
user_kmp = get_installed_kmp(InstallArea.IA_USER)
shared_kmp = get_installed_kmp(InstallArea.IA_SHARED)
os_kmp = get_installed_kmp(InstallArea.IA_OS)
user_kmp = get_installed_kmp(InstallLocation.User)
shared_kmp = get_installed_kmp(InstallLocation.Shared)
os_kmp = get_installed_kmp(InstallLocation.OS)
if packageID in os_kmp:
version = os_kmp[packageID]['version']
@ -180,7 +153,7 @@ def get_kmp_version_user(packageID):
str: kmp version if kmp ID is installed
None: if not found
"""
user_kmp = get_installed_kmp(InstallArea.IA_USER)
user_kmp = get_installed_kmp(InstallLocation.User)
if packageID in user_kmp:
return user_kmp[packageID]['version']
else:

View file

@ -4,7 +4,8 @@ import logging
import os.path
from shutil import rmtree
from keyman_config.get_kmp import user_keyboard_dir, user_keyman_font_dir
from keyman_config.fcitx_util import is_fcitx_running
from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_doc_dir, get_keyman_font_dir
from keyman_config.kmpmetadata import get_metadata
from keyman_config.ibus_util import uninstall_from_ibus, get_ibus_bus, restart_ibus
from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, get_ibus_keyboard_id, is_gnome_shell
@ -17,13 +18,13 @@ def uninstall_kmp_shared(packageID):
Args:
packageID (str): Keyboard package ID
"""
kbdir = os.path.join('/usr/local/share/keyman', packageID)
kbdir = get_keyboard_dir(InstallLocation.Shared, packageID)
if not os.path.isdir(kbdir):
logging.error("Keyboard directory for %s does not exist. Aborting", packageID)
exit(3)
kbdocdir = os.path.join('/usr/local/share/doc/keyman', packageID)
kbfontdir = os.path.join('/usr/local/share/fonts/keyman', packageID)
kbdocdir = get_keyman_doc_dir(InstallLocation.Shared, packageID)
kbfontdir = get_keyman_font_dir(InstallLocation.Shared, packageID)
logging.info("Uninstalling shared keyboard: %s", packageID)
if not os.access(kbdir, os.X_OK | os.W_OK): # Check for write access of keyman dir
@ -99,6 +100,10 @@ def uninstall_keyboards_from_gnome(keyboards, packageDir):
gnomeKeyboardsUtil.write_input_sources(sources)
def _uninstall_keyboards_from_fcitx5():
return _('Please use fcitx5-configtool to remove the keyboard from the group')
def uninstall_kmp_user(packageID):
"""
Uninstall a kmp from ~/.local/share/keyman
@ -106,14 +111,16 @@ def uninstall_kmp_user(packageID):
Args:
packageID (str): Keyboard package ID
"""
kbdir = user_keyboard_dir(packageID)
kbdir = get_keyboard_dir(InstallLocation.User, packageID)
if not os.path.isdir(kbdir):
logging.error("Keyboard directory for %s does not exist. Aborting", packageID)
exit(3)
logging.info("Uninstalling local keyboard: %s", packageID)
info, system, options, keyboards, files = get_metadata(kbdir)
if keyboards:
if is_gnome_shell():
if is_fcitx_running():
_uninstall_keyboards_from_fcitx5()
elif is_gnome_shell():
uninstall_keyboards_from_gnome(keyboards, kbdir)
else:
uninstall_keyboards_from_ibus(keyboards, kbdir)
@ -121,7 +128,7 @@ def uninstall_kmp_user(packageID):
logging.warning("could not uninstall keyboards")
rmtree(kbdir)
logging.info("Removed user keyman directory: %s", kbdir)
fontdir = os.path.join(user_keyman_font_dir(), packageID)
fontdir = get_keyman_font_dir(InstallLocation.User, packageID)
if os.path.isdir(fontdir):
rmtree(fontdir)
logging.info("Removed user keyman font directory: %s", fontdir)

View file

@ -7,13 +7,14 @@ import subprocess
import sys
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, GdkPixbuf
from keyman_config import _
from keyman_config.list_installed_kmp import get_install_area_path, get_installed_kmp, InstallArea
from keyman_config.list_installed_kmp import get_installed_kmp
from keyman_config.welcome import WelcomeView
from keyman_config.options import OptionsView
from keyman_config.keyboard_details import KeyboardDetailsView
@ -21,7 +22,7 @@ from keyman_config.downloadkeyboard import DownloadKmpWindow
from keyman_config.install_window import InstallKmpWindow, find_keyman_image
from keyman_config.uninstall_kmp import uninstall_kmp
from keyman_config.accelerators import bind_accelerator, init_accel
from keyman_config.get_kmp import user_keyboard_dir
from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir
class ViewInstalledWindowBase(Gtk.Window):
@ -122,7 +123,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
str, # name
str, # version
str, # packageID
int, # enum InstallArea (KmpArea is GObject version)
int, # enum InstallLocation (KmpArea is GObject version)
str, # path to welcome file if it exists or None
str) # path to options file if it exists or None
@ -219,12 +220,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
kmpdata = installed_kmp[kmp]
bmppng = ".bmp.png" # Icon file extension
if install_area == InstallArea.IA_USER:
path = user_keyboard_dir(kmpdata['packageID'])
elif install_area == InstallArea.IA_SHARED:
path = os.path.join("/usr/local/share/keyman", kmpdata['packageID'])
else:
path = os.path.join("/usr/share/keyman", kmpdata['packageID'])
path = get_keyboard_dir(install_area, kmpdata['packageID'])
welcome_file = os.path.join(path, "welcome.htm")
options_file = os.path.join(path, "options.htm")
@ -252,24 +248,24 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
logging.debug("Refreshing listview")
self.store.clear()
self.incomplete_kmp = []
user_kmp = get_installed_kmp(InstallArea.IA_USER)
user_kmp = get_installed_kmp(InstallLocation.User)
for kmp in sorted(user_kmp):
kmpdata = user_kmp[kmp]
if kmpdata["has_kbjson"] is False:
self.incomplete_kmp.append(kmpdata)
self.addlistitems(user_kmp, self.store, InstallArea.IA_USER)
shared_kmp = get_installed_kmp(InstallArea.IA_SHARED)
self.addlistitems(user_kmp, self.store, InstallLocation.User)
shared_kmp = get_installed_kmp(InstallLocation.Shared)
for kmp in sorted(shared_kmp):
kmpdata = shared_kmp[kmp]
if kmpdata["has_kbjson"] is False:
self.incomplete_kmp.append(kmpdata)
self.addlistitems(shared_kmp, self.store, InstallArea.IA_SHARED)
os_kmp = get_installed_kmp(InstallArea.IA_OS)
self.addlistitems(shared_kmp, self.store, InstallLocation.Shared)
os_kmp = get_installed_kmp(InstallLocation.OS)
for kmp in sorted(os_kmp):
kmpdata = os_kmp[kmp]
if kmpdata["has_kbjson"] is False:
self.incomplete_kmp.append(kmpdata)
self.addlistitems(os_kmp, self.store, InstallArea.IA_OS)
self.addlistitems(os_kmp, self.store, InstallLocation.OS)
def on_tree_selection_changed(self, selection):
model, treeiter = selection.get_selected()
@ -284,7 +280,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
_("Settings for keyboard {package}").format(package=model[treeiter][1]))
logging.debug("You selected %s version %s", model[treeiter][1], model[treeiter][2])
self.about_button.set_sensitive(True)
if model[treeiter][4] == InstallArea.IA_USER:
if model[treeiter][4] == InstallLocation.User:
logging.debug("Enabling uninstall button for %s in %s", model[treeiter][3], model[treeiter][4])
self.uninstall_button.set_sensitive(True)
else:
@ -365,7 +361,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
model, treeiter = self.tree.get_selection().get_selected()
if treeiter is not None and model[treeiter] is not None:
logging.info("Show keyboard details of " + model[treeiter][1])
areapath = get_install_area_path(model[treeiter][4])
areapath = get_keyman_dir(model[treeiter][4])
kmp = {
"name": model[treeiter][1], "version": model[treeiter][2],
"packageID": model[treeiter][3], "areapath": areapath

View file

@ -16,7 +16,10 @@ if __name__ == '__main__':
parser.add_argument('--version', action='version', version='%(prog)s version ' + versionstring)
parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging')
parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging')
parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp package')
parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp ' +
'package. INSTALL can either be a downloaded .kmp file, a file:// URL ' +
'pointing to a .kmp file, or a keyman:// URL, possibly with a ' +
'bcp47=<language> specified.')
args = parser.parse_args()
if args.verbose:

View file

@ -0,0 +1,36 @@
#/usr/bin/env bash
_km-config_completions()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-h --help -v --verbose -vv --veryverbose --version -i --install"
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# default IFS splits 'file://' into 'file', ':' and '//'
if [ "${prev}" == ":" -a "${COMP_WORDS[COMP_CWORD-2]}" == "file" ]; then
local flag=${COMP_WORDS[COMP_CWORD-3]}
if [ $flag == "-i" -o $flag == "--install" ]; then
prev=$flag
fi
fi
case "${prev}" in
"-i"|"--install")
local IFS=$'\n'
compopt -o filenames
COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) )
return 0
;;
*)
;;
esac
}
complete -F _km-config_completions km-config

View file

@ -12,6 +12,21 @@ _km-kvk2ldml_completions()
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
"-o"|"--output")
local IFS=$'\n'
compopt -o filenames
COMPREPLY=( $(compgen -f -- $cur) )
return 0
;;
*)
;;
esac
local IFS=$'\n'
compopt -o filenames
COMPREPLY=( $(compgen -f -X "!"*.kvk -- $cur) $(compgen -d -- $cur) )
}
complete -F _km-kvk2ldml_completions km-kvk2ldml

View file

@ -2,31 +2,33 @@
_km-package-get_completions()
{
local cur prev opts
local cur prev opts cache
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-h --help -v --verbose -vv --veryverbose --version"
cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
words=""
if [[ ! -e ~/.cache/keyman/kmpdirlist ]] ; then
if [[ -e ./km-package-install ]]; then
python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()"
else
python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()"
fi
fi
words=""
if [[ ! -e $cache ]] ; then
if [[ -e ./km-package-install ]]; then
python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()"
else
python3 -c "from imp import load_source;load_source('km_package_install', '/usr/bin/km-package-install');from km_package_install import list_keyboards;list_keyboards()"
fi
fi
if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then
for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
return 0
fi
if [[ -r $cache ]] ; then
for file in `cat $cache`; do words="${words} ${file}"; done
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
return 0
fi
}
complete -F _km-package-get_completions km-package-get

View file

@ -2,21 +2,23 @@
_km-package-install_completions()
{
local cur prev opts
local cur prev opts cache
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-h --help -v --verbose -vv --veryverbose --version -p --package -f --file -s --shared"
cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
"-k"|"--keyboardid")
"-p"|"--package")
words=""
if [[ ! -s ~/.cache/keyman/kmpdirlist ]] ; then
if [[ ! -s $cache ]] ; then
if [[ -e ./km-package-install ]]; then
python3 -c "from imp import load_source;load_source('km_package_install', './km-package-install');from km_package_install import list_keyboards;list_keyboards()"
else
@ -24,12 +26,18 @@ _km-package-install_completions()
fi
fi
if [[ -r ~/.cache/keyman/kmpdirlist ]] ; then
for file in `cat ~/.cache/keyman/kmpdirlist`; do words="${words} ${file}"; done
if [[ -r $cache ]] ; then
for file in `cat $cache`; do words="${words} ${file}"; done
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
return 0
fi
;;
"-f"|"--file")
local IFS=$'\n'
compopt -o filenames
COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) )
return 0
;;
*)
;;
esac

View file

@ -25,26 +25,27 @@ def main():
logging.basicConfig(format='%(levelname)s:%(message)s')
all = not args.user and not args.shared and not args.os
from keyman_config.list_installed_kmp import get_installed_kmp, get_install_area_path, InstallArea
from keyman_config.list_installed_kmp import get_installed_kmp
from keyman_config.get_kmp import get_keyman_dir, InstallLocation
if args.user or all:
installed_kmp = get_installed_kmp(InstallArea.IA_USER)
installed_kmp = get_installed_kmp(InstallLocation.User)
if args.verbose or args.veryverbose:
print("--- Installed user Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_USER)))
print("--- Installed user Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.User)))
else:
print("--- Installed user Keyman keyboard packages ---")
print_keyboards(installed_kmp, args.long)
if args.shared or all:
installed_kmp = get_installed_kmp(InstallArea.IA_SHARED)
installed_kmp = get_installed_kmp(InstallLocation.Shared)
if args.verbose or args.veryverbose:
print("--- Installed shared Keyman keyboard packages (%s) ---" %
(get_install_area_path(InstallArea.IA_SHARED)))
(get_keyman_dir(InstallLocation.Shared)))
else:
print("--- Installed shared Keyman keyboard packages ---")
print_keyboards(installed_kmp, args.long)
if args.os or all:
installed_kmp = get_installed_kmp(InstallArea.IA_OS)
installed_kmp = get_installed_kmp(InstallLocation.OS)
if args.verbose or args.veryverbose:
print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_OS)))
print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.OS)))
else:
print("--- Installed OS Keyman keyboard packages ---")
print_keyboards(installed_kmp, args.long)

View file

@ -23,11 +23,11 @@ _km-package-uninstall_completions()
shared=""
case "${prev}" in
"-s"|"--shared")
for file in `ls -d /usr/local/share/keyman/*/`; do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done
for file in $(ls -1 /usr/local/share/keyman); do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done
COMPREPLY=($(compgen -W "${shared}" -- ${cur}))
;;
*)
for file in `ls -d ~/.local/share/keyman/*/`; do kbid="`basename ${file}`"; words="${words} ${kbid}"; done
for file in $(ls -1 ${XDG_DATA_HOME:-~/.local/share}/keyman); do kbid="`basename ${file}`"; words="${words} ${kbid}"; done
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
;;
esac

View file

@ -3,7 +3,7 @@
# If needed set cowbuilder up for building Keyman Debian packages
# Then cowbuilder update
distributions='hirsute groovy focal bionic'
distributions='bionic focal groovy hirsute impish'
dpkgcheck=`dpkg-query -l cowbuilder`
if [ $? != 0 ]; then

View file

@ -40,7 +40,7 @@ echo "ppa: ${ppa}"
if [ "${DIST}" != "" ]; then
distributions="${DIST}"
else
distributions="bionic focal groovy hirsute"
distributions="bionic focal groovy hirsute impish"
fi
if [ "${PACKAGEVERSION}" != "" ]; then

View file

@ -1,13 +1,5 @@
# Keyman for macOS
## Mac Tools Requirements/Setup
* Install [Homebrew] *technically optional, but highly recommended*
* Install Xcode 12.0+
* Accept the Xcode license `sudo xcodebuild -license accept`
* Install [Carthage] (`brew install carthage`) *see Homebrew note below*
* Install [cocoapods] (`brew install cocoapods`)
* Install [coreutils] (`brew install coreutils`)
## Keyman for macOS Development
@ -120,9 +112,4 @@ Then run this command to fix the build environment:
`sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`
[Homebrew]: https://brew.sh/
[Carthage]: https://github.com/Carthage/Carthage/blob/master/README.md
[cocoapods]: https://cocoapods.org/
[coreutils]: https://www.gnu.org/software/coreutils/
[Install Keyman for macOS]: https://help.keyman.com/products/mac/current-version/start/install-keyman

View file

@ -0,0 +1,19 @@
source $HOME/.cargo/env
#echo 'export PATH="/usr/local/opt/openjdk@8/bin:$PATH"' >> ~/.zshrc
#echo 'export PATH="/usr/local/opt/openjdk@8/bin:$PATH"' >> ~/.bashrc
export ANT_HOME=/usr/local/opt/ant
export MAVEN_HOME=/usr/local/opt/maven
export GRADLE_HOME=/usr/local/opt/gradle
export ANDROID_HOME=~/.android
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"
export PATH=$ANT_HOME/bin:$PATH
export PATH=$MAVEN_HOME/bin:$PATH
export PATH=$GRADLE_HOME/bin:$PATH
export PATH=$ANDROID_HOME/tools:$PATH
export PATH=$ANDROID_HOME/tools/bin:$PATH
export PATH=$ANDROID_HOME/platform-tools:$PATH
export PATH=$ANDROID_HOME/build-tools/30.0.3:$PATH
export PATH="/usr/local/opt/openjdk@8/bin:$PATH"
export JAVA_HOME=/usr/local/opt/openjdk@8

224
resources/devbox/macos/macos.sh Executable file
View file

@ -0,0 +1,224 @@
#!/usr/bin/env bash
set -e
set -u
## Note: does not use standard build script include
# adjust relative paths as necessary. Note that this will not work in a symlinked path reliably
THIS_SCRIPT="${BASH_SOURCE[0]}"
THIS_DIR=$(dirname "$THIS_SCRIPT")
# Determine which components to install
REQUIRE_ANDROID=false
REQUIRE_IOS=false
REQUIRE_MACOS=false
REQUIRE_WEB=false
# Optional components
REQUIRE_KMCOMP=false
REQUIRE_PANDOC=false
REQUIRE_SENTRYCLI=false
# Parse args
shopt -s nocasematch
PARAMFOUND=false
function print_help() {
echo "Usage: macos.sh targets"
echo
echo "The targets parameter determines the platforms and components"
echo "that this script will setup your environment to build."
echo
echo " group targets:"
echo " all build all platform targets"
echo " all-optional build all targets, including optional"
echo
echo " platform targets:"
echo " android ios macos web"
echo
echo " optional targets:"
echo " kmcomp Keyman keyboard compiler"
echo " pandoc Documentation compiler"
echo " sentry-cli sentry.keyman.com debug symbol uploader"
echo
echo "Note: If a target has dependencies, those will automatically"
echo " be included."
}
while [[ $# -gt 0 ]] ; do
key="$1"
case $key in
--help)
print_help
exit 0
;;
android)
REQUIRE_ANDROID=true
PARAMFOUND=true
;;
ios)
REQUIRE_IOS=true
PARAMFOUND=true
;;
macos)
REQUIRE_MACOS=true
PARAMFOUND=true
;;
web)
REQUIRE_WEB=true
PARAMFOUND=true
;;
kmcomp)
REQUIRE_KMCOMP=true
PARAMFOUND=true
;;
pandoc)
REQUIRE_PANDOC=true
PARAMFOUND=true
;;
sentry-cli)
REQUIRE_SENTRYCLI=true
PARAMFOUND=true
;;
all)
REQUIRE_ANDROID=true
REQUIRE_IOS=true
REQUIRE_MACOS=true
REQUIRE_WEB=true
PARAMFOUND=true
;;
all-optional)
REQUIRE_ANDROID=true
REQUIRE_IOS=true
REQUIRE_MACOS=true
REQUIRE_WEB=true
REQUIRE_KMCOMP=true
REQUIRE_PANDOC=true
REQUIRE_SENTRYCLI=true
PARAMFOUND=true
;;
*)
echo "Error: unrecognised parameter."
echo
print_help
exit 1
;;
esac
shift
done
if ! $PARAMFOUND; then
echo "Error: must specify target parameter"
echo
print_help
exit 1
fi
# This script will configure your development environment from a bare metal mac install. It should be idempotent.
echo "This script will configure your macOS computer to build Keyman, installing build tools and prerequisites."
echo "You can also do this yourself following the notes in docs/build/macos.md."
echo
read -p "Press ENTER to start install"
if $REQUIRE_IOS || $REQUIRE_ANDROID; then
REQUIRE_WEB=true
fi
if $REQUIRE_IOS || $REQUIRE_MACOS || $REQUIRE_ANDROID; then
REQUIRE_PANDOC=true
fi
## These components are required by all development toolchains
# XCode command lines tools will be installed by Brew
# xcode-select --install
which brew || (
# Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
) && (
brew update
)
## Install devchain components
BREW_ALL="bash jq python3 meson ninja rustup-init coreutils"
BREW_WEB="node emscripten wasm-pack openjdk@8"
BREW_IOS="swiftlint carthage"
BREW_MACOS="carthage cocoapods"
BREW_ANDROID="openjdk@8 android-sdk android-studio ant gradle maven"
brew install $BREW_ALL
$REQUIRE_ANDROID && brew install $BREW_ANDROID
$REQUIRE_IOS && brew install $BREW_IOS
$REQUIRE_WEB && brew install $BREW_WEB
$REQUIRE_MACOS && brew install $BREW_MACOS
$REQUIRE_PANDOC && brew install pandoc
$REQUIRE_SENTRYCLI && brew install getsentry/tools/sentry-cli
# Install WINE
$REQUIRE_KMCOMP && (
brew tap homebrew/cask-versions
brew install --cask --no-quarantine wine-stable
)
$REQUIRE_WEB && (
rustup-init -y -t wasm32-unknown-unknown
)
source "$THIS_DIR/keyman.macos.env.sh"
$REQUIRE_ANDROID && (
mkdir -p .android && touch ~/.android/repositories.cfg
which sdkmanager || (
echo "Please run Android Studio to install the Android SDK tools."
read -p "Press ENTER to continue after installation"
PATH="$HOME/Library/Android/sdk/tools/bin:$PATH"
)
sdkmanager "system-images;android-30;google_apis_playstore;x86_64"
sdkmanager --update
sdkmanager --licenses
)
# For now, we won't run this step automatically
# as it may interfere with other dev environments
#($REQUIRE_IOS || $REQUIRE_MACOS) && (
# # Assumes that xcode is installed into normal path
# # as otherwise we get only the command line tools which
# # won't build with xcode
# echo "Selecting default xcodebuild command line tools from /Applications/Xcode.app"
# sudo xcode-select -s /Applications/Xcode.app
#)
# Add keyman.macos.env.sh to ~/.bashrc
echo "Adding environment variables to ~/.bashrc..."
if [ ! -f ~/.bashrc ] || `grep "keyman.macos.env.sh" ~/.bashrc 2>/dev/null`; then
echo "source $THIS_DIR/keyman.macos.env.sh" >> ~/.bashrc
fi
echo "Configuration has completed successfully."
echo
if $REQUIRE_MACOS || $REQUIRE_IOS; then
echo "The following components must be installed manually:"
echo " * XCode"
echo
fi
if $REQUIRE_MACOS || $REQUIRE_IOS || $REQUIRE_ANDROID; then
echo "The following components should be started manually after this script completes, in order to install"
echo "additional components:"
if $REQUIRE_MACOS || $REQUIRE_IOS; then
echo " * XCode"
echo "You may need to run xcode-select to choose the correct version of XCode command line tools."
fi
if $REQUIRE_ANDROID; then
echo " * Android Studio"
fi
echo
fi

View file

@ -1,5 +1,5 @@
{
"name": "@keymanapp/resources-gosh",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/resources-gosh",
"version": "15.0.74",
"version": "15.0.79",
"description": "Good Ol' Shell",
"author": "Marc Durdin <marc@keyman.com> (https://github.com/mcdurdin)",
"contributors": [

View file

@ -1,5 +1,5 @@
{
"name": "@keymanapp/web-environment",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 1
}

View file

@ -1,6 +1,6 @@
{
"name": "@keymanapp/web-environment",
"version": "15.0.74",
"version": "15.0.79",
"description": "A compilation utility for passing select environment variables into our TS builds",
"author": "Joshua Horton <joshua_horton@sil.org> (https://github.com/jahorton)",
"homepage": "https://github.com/keymanapp/keyman",
@ -17,6 +17,6 @@
"url": "https://github.com/keymanapp/keyman/issues"
},
"devDependencies": {
"@keymanapp/resources-gosh": "^15.0.74"
"@keymanapp/resources-gosh": "^15.0.79"
}
}

View file

@ -1,20 +1,8 @@
# Keyman Web
The Original Code is (C) 2017-2018 SIL International
The Original Code is (C) SIL International
## Minimum Web Compilation Requirements
* [Java 7+](https://adoptopenjdk.net/releases.html) (required by the Google Closure Compiler)
* This can be ignored for Keyman Developer builds.
* A local installation of [Node.js](https://nodejs.org/) v8.9+.
* Builds will call `npm install` to automatically install further necessary build dependencies.
* Linux users can run the following to update to LTS version of nodejs
```
sudo apt-get install python-software-properties
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install nodejs
```
## Prerequisites
See [build configuration](../docs/build/index.md) for details on how to configure your build environment.
**********************************************************************

2
web/package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "keyman",
"version": "15.0.74",
"version": "15.0.79",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "keyman",
"version": "15.0.74",
"version": "15.0.79",
"description": "Facilitates text input in any language.",
"repository": {
"type": "git",
@ -18,8 +18,8 @@
},
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/resources-gosh": "^15.0.74",
"@keymanapp/web-sentry-manager": "^15.0.74",
"@keymanapp/resources-gosh": "^15.0.79",
"@keymanapp/web-sentry-manager": "^15.0.79",
"@sentry/cli": "^1.52.3",
"chai": "^4.3.4",
"google-closure-compiler-java": "^20200224.0.0",
@ -52,13 +52,13 @@
"sentry-cli": "sentry-cli"
},
"dependencies": {
"@keymanapp/input-processor": "^15.0.74",
"@keymanapp/keyboard-processor": "^15.0.74",
"@keymanapp/lexical-model-layer": "^15.0.74",
"@keymanapp/models-types": "^15.0.74",
"@keymanapp/recorder-core": "^15.0.74",
"@keymanapp/web-environment": "^15.0.74",
"@keymanapp/web-utils": "^15.0.74",
"@keymanapp/input-processor": "^15.0.79",
"@keymanapp/keyboard-processor": "^15.0.79",
"@keymanapp/lexical-model-layer": "^15.0.79",
"@keymanapp/models-types": "^15.0.79",
"@keymanapp/recorder-core": "^15.0.79",
"@keymanapp/web-environment": "^15.0.79",
"@keymanapp/web-utils": "^15.0.79",
"@types/node": "^11.9.4",
"es6-shim": "^0.35.5",
"eventemitter3": "^4.0.0"

View file

@ -1474,10 +1474,10 @@ namespace com.keyman.dom {
/**
* Function Initialization
* Scope Public
* @param {Object} arg object array of user-defined properties
* @param {com.keyman.OptionType} arg object of user-defined properties
* Description KMW window initialization
*/
init: (arg:any) => Promise<any> = function(this: DOMManager, arg): Promise<any> {
init: (arg: com.keyman.OptionType) => Promise<any> = function(this: DOMManager, arg): Promise<any> {
var i,j,c,e,p,eTextArea,eInput,opt,dTrailer,ds;
var osk = this.keyman.osk;
var util = this.keyman.util;

View file

@ -1,4 +1,6 @@
/// <reference path="../kmwbase.ts" />
// Includes a promise polyfill (needed for IE)
/// <reference path="../../node_modules/es6-shim/es6-shim.min.js" />
namespace com.keyman.keyboards {
class CloudRequestEntry {
@ -68,6 +70,15 @@ namespace com.keyman.keyboards {
}
}
// Information about a keyboard that fails to get added
export interface ErrorStub {
language?: {
id?: string;
name?: string;
}
error: Error;
}
export class KeyboardTag {
stores: {[text: string]: text.ComplexKeyboardStore} = {};
}
@ -85,15 +96,14 @@ namespace com.keyman.keyboards {
// Represents unspecified errors that occur when registering the results of a successful API call.
const CLOUD_STUB_REGISTRATION_ERR = "The Cloud API failed to find an appropriate keyboard.";
// Represents custom, specified KMW errors that occur when registering the results of a successful API call.
const CLOUD_REGISTRATION_ERROR = "Error occurred while registering keyboards: ";
const CLOUD_REGISTRATION_ERR = "Error occurred while registering keyboards: ";
const MISSING_KEYBOARD = function(kbdid: string) {
return kbdid + ' keyboard not found.';
}
interface RegistrationPromiseTuple {
// TODO (#5044): the parameter for `resolve` should match the type of the Promise in keymanCloudRequest.
resolve: () => void;
resolve: (stubs: KeyboardStub[]) => void;
reject: (err: Error) => void;
}
@ -107,6 +117,8 @@ namespace com.keyman.keyboards {
activeStub: KeyboardStub = null;
keyboardStubs: KeyboardStub[] = [];
firstCall: boolean = true; // First time to call keymanCloudRequest()
deferredStubs: any[] = []; // The list of user-provided keyboard stub registration objects.
deferredKRS = []; // Array of pending keyboard stubs from KRS, to register after initialization
deferredKR = []; // Array of pending keyboards, to be installed at end of initialization
@ -123,8 +135,9 @@ namespace com.keyman.keyboards {
*/
registrationResolvers: {[timeoutID: number] : RegistrationPromiseTuple} = {};
languageListPromise: Promise<(KeyboardStub|ErrorStub)[]> = null;
languageList: any[] = null; // List of keyboard languages available for KeymanCloud
languagesPending: any[] = []; // Array of languages waiting to be registered
linkedScripts: HTMLScriptElement[] = [];
@ -566,8 +579,8 @@ namespace com.keyman.keyboards {
// Thanks, Closure errors.
if(!this.keymanweb.isEmbedded) {
util.wait(false);
util.alert(altString || msg, function() {
this.keymanweb['setActiveKeyboard'](''); // The API call!
util.internalAlert(altString || msg, function() {
this.keymanweb['setActiveKeyboard'](''); // The API call!
}.bind(this));
}
@ -943,7 +956,7 @@ namespace com.keyman.keyboards {
if(typeof(x[i]['filename']) == 'string') {
if(!this.addStub(x[i])) {
alert('To use a custom keyboard, you must specify file name, keyboard name, language, language code and region code.');
this.keymanweb.util.internalAlert('To use a custom keyboard, you must specify file name, keyboard name, language, language code and region code.');
}
} else {
if(x[i]['language']) {
@ -984,7 +997,11 @@ namespace com.keyman.keyboards {
}
// Request keyboard metadata from the Keyman Cloud keyboard metadata server
this.keymanCloudRequest(cmd,false);
try {
let promise = this.keymanCloudRequest(cmd,false);
} catch(err) {
console.error(err);
}
}
/**
@ -1067,11 +1084,11 @@ namespace com.keyman.keyboards {
register(x) {
const promiseid = x['timerid'];
let result: Error;
let result: KeyboardStub[] | Error;
try {
result = this._registerCore(x);
} catch(err) {
result = new Error(CLOUD_REGISTRATION_ERROR + err);
result = new Error(CLOUD_REGISTRATION_ERR + err);
}
if(promiseid) {
@ -1081,8 +1098,8 @@ namespace com.keyman.keyboards {
try {
if(result instanceof Error) {
promiseFuncs.reject(result as Error);
} else { // TODO (#5044): pass `result` to the `resolve` function.
promiseFuncs.resolve();
} else {
promiseFuncs.resolve(result as KeyboardStub[]);
}
} finally {
delete this.registrationResolvers[promiseid];
@ -1095,8 +1112,9 @@ namespace com.keyman.keyboards {
*
* @param {Object} x metadata object
**/
private _registerCore(x): Error { // TODO (#5044): should return heterogenous type; allow array of stubs.
private _registerCore(x): KeyboardStub[] | Error { // TODO (#5044): should return heterogenous type; allow array of stubs.
var options=x['options'];
let currentKeyboardStubsCount = this.keyboardStubs.length;
// Indicate if unable to register keyboard
if(typeof(x['error']) == 'string') {
@ -1129,34 +1147,60 @@ namespace com.keyman.keyboards {
}
} else if(options['context'] == 'language') { // Download the full list of supported keyboard languages
this.languageList = x['languages'];
if(this.languagesPending) {
this.addLanguageKeyboards(this.languagesPending);
}
this.languagesPending = [];
}
return null; // TODO (#5044): return the list of newly-registered stubs here.
return this.keyboardStubs.slice(currentKeyboardStubsCount);
}
/**
* Internal handler for processing keyboard registration, used only by `register`
*
* @param {Object} languages Array of language names
* @param {string[]} languages Array of language names
* @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard stubs
**/
addLanguageKeyboards(languages) {
var i, j, lgName, cmd, first, addAll;
async addLanguageKeyboards(languages: string[]): Promise<(KeyboardStub|ErrorStub)[]> {
var i, j, lgName, cmd, addAll, retPromise;
let errorStub: ErrorStub[] = [];
// Defer registering keyboards by language until the language list has been loaded
if(this.languageList == null) {
first = (this.languagesPending.length == 0);
for(i=0; i<languages.length; i++) {
this.languagesPending.push(languages[i]);
if (this.languageList == null) {
if (this.firstCall) {
this.firstCall = false;
let promise = this.keymanCloudRequest('',true);
// If promise is not error, then... (needs an error guard)
promise.catch(function(error) {
let msg = "Unable to retrieve the master language list.";
console.error(msg, error)
let stub: ErrorStub = {error: new Error(msg)};
errorStub.push(stub);
return Promise.reject(errorStub);
});
this.languageListPromise = promise;
}
if(first) {
this.keymanCloudRequest('',true);
}
let _this = this;
retPromise = new Promise(function(resolve, reject) {
if (_this.languageListPromise) {
// 1: wait for the language list to be loaded properly
_this.languageListPromise.then(function() {
// 2: perform the actual query, now that we can find the language code
resolve(_this.addLanguageKeyboards(languages));
}).catch(function(error) {
return Promise.reject(error);
});
} else {
return _this.addLanguageKeyboards(languages);
}
}).then(function(result) {
if (result instanceof Error) {
return Promise.reject(result);
}
return Promise.resolve(result);
}).catch(function(error) {
return Promise.reject(error);
});
} else { // Identify and register each keyboard by language name
cmd = '';
for(i=0; i<languages.length; i++) {
@ -1166,6 +1210,7 @@ namespace com.keyman.keyboards {
lgName = lgName.substr(0,lgName.length-1);
}
let languageFound: boolean = false;
for(j=0; j<this.languageList.length; j++) {
if(lgName == this.languageList[j]['name'].toLowerCase()) {
if(cmd != '') {
@ -1177,18 +1222,49 @@ namespace com.keyman.keyboards {
cmd = cmd + '$';
}
languageFound = true;
break;
}
}
if (!languageFound) {
// Construct response array of errors (failed-query keyboards)
// that will be merged with stubs (successfully-queried keyboards)
let stub: ErrorStub = {language: {name: lgName}, error: new Error(this.alertLanguageUnavailable(lgName))};
errorStub.push(stub);
}
}
if(cmd == '') {
this.keymanweb.util.alert('No keyboards are available for '+languages[0]+'. '
+'Does it have another language name?');
} else {
this.keymanCloudRequest('&keyboardid='+cmd,false);
// No command so return errors
return Promise.reject(errorStub);
}
try {
// Merge this with errorStub
let result:(KeyboardStub|ErrorStub)[]|Error = await this.keymanCloudRequest('&keyboardid='+cmd, false);
if (Array.isArray(result)) {
if (errorStub.length > 0) {
result = result.concat(errorStub);
return Promise.resolve(result);
} else {
return Promise.resolve(result);
}
}
} catch(err) {
// We don't have language info for this ErrorStub
console.error(err);
let stub: ErrorStub = {error: err};
errorStub.push(stub);
return Promise.reject(errorStub);
}
}
if (retPromise) {
return retPromise;
} else {
// No keyboards added so return empty stub
return Promise.resolve(errorStub);
}
}
/**
@ -1196,8 +1272,9 @@ namespace com.keyman.keyboards {
*
* @param {string} cmd command string
* @param {boolean?} byLanguage if true, context=languages, else context=keyboards
* @returns {Promise<(KeyboardStub[]>} Promise of added keyboard stubs
**/
keymanCloudRequest(cmd: string, byLanguage?: boolean) {
keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise<KeyboardStub[]> {
var kbdManager = this;
var keymanweb = this.keymanweb;
@ -1208,7 +1285,7 @@ namespace com.keyman.keyboards {
const URL='https://api.keyman.com/cloud/4.0/'
+ ((arguments.length > 1) && byLanguage ? 'languages' : 'keyboards');
let promise = new Promise<void>(function(resolve: () => void, reject: (Error?) => void) {
let promise = new Promise(function(resolve: (result: KeyboardStub[]) => void, reject: (error: Error) => void) {
const Lscript: HTMLScriptElement = keymanweb.util._CreateElement('script');
const queryConfig = '?jsonp=keyman.register&languageidtype=bcp47&version='+keymanweb['version'];
@ -1216,7 +1293,7 @@ namespace com.keyman.keyboards {
// Set callback timer
const timeoutID = window.setTimeout(function() {
delete kbdManager.registrationResolvers[timeoutID];
reject(CLOUD_TIMEOUT_ERR);
reject(new Error(CLOUD_TIMEOUT_ERR));
} ,10000);
// Save the resolve / reject functions.
@ -1269,13 +1346,26 @@ namespace com.keyman.keyboards {
document.getElementsByTagName('head')[0].appendChild(Lscript);
}
});
// TODO: Allow the site developer to handle error messaging via this catch.
// This current version simply maintains pre-existing behavior.
promise.catch(function(error: Error) {
kbdManager.serverUnavailable(error);
throw error;
kbdManager.serverUnavailable(error.message);
return Promise.reject(error);
});
return promise;
}
/**
* Display warning if language name unavailable to add keyboard
* @param {string} languageName
* @returns string of Error message
*/
private alertLanguageUnavailable(languageName: string): string {
let msg = 'No keyboards are available for '+ languageName + '. '
+'Does it have another language name?';
this.keymanweb.util.internalAlert(msg);
return msg;
}
/**
@ -1283,9 +1373,9 @@ namespace com.keyman.keyboards {
*
* @param {string} cmd command string sent to Cloud
*
**/
private serverUnavailable(cmd) {
this.keymanweb.util.alert(cmd == '' ? 'Unable to connect to Keyman Cloud server!' : cmd);
*/
private serverUnavailable(cmd: string): void {
this.keymanweb.util.internalAlert(cmd == '' ? 'Unable to connect to Keyman Cloud server!' : cmd);
this.keymanweb.warned=true;
}
@ -1578,4 +1668,4 @@ namespace com.keyman.keyboards {
}
}
}
}
}

View file

@ -28,6 +28,20 @@
Copyright 2017-2019 SIL International
***/
namespace com.keyman {
export interface OptionType {
root?: string;
resources?: string;
keyboards?: string;
fonts?: string;
attachType?: 'auto' | 'manual' | ''; // If blank or undefined, attachType will be assigned to "auto" or "manual"
ui?: string;
setActiveOnRegister?: string; // TODO: Convert to boolean. Option loader needs to be able to receive this as a string or boolean
// Determines whether or not KeymanWeb should display its own alert messages
useAlerts?: boolean;
}
export class KeymanBase {
_TitleElement = null; // I1972 - KeymanWeb Titlebar should not be a link
_IE = 0; // browser version identification
@ -73,19 +87,21 @@ namespace com.keyman {
touchAliasing: dom.DOMEventHandlers;
// Defines option-tracking object as a string map.
options: { [name: string]: string; } = {
'root':'',
'resources':'',
'keyboards':'',
'fonts':'',
'attachType':'',
'ui':null,
'setActiveOnRegister':'true'
options: OptionType = {
root: '',
resources: '',
keyboards: '',
fonts: '',
attachType: '',
ui: null,
setActiveOnRegister: 'true', // TODO: convert to boolean
// Determines whether or not KeymanWeb should display its own alert messages
useAlerts: true
};
// Stub functions (defined later in code only if required)
setDefaultDeviceOptions(opt){}
setDefaultDeviceOptions(opt: OptionType){}
getStyleSheetPath(s){return s;}
getKeyboardPath(f, p?){return f;}
KC_(n, ln, Pelem){return '';}
@ -303,12 +319,17 @@ namespace com.keyman {
}
/**
* Add default or all keyboards for a given language
* Add default keyboards for given language(s)
*
* @param {string} arg Language name (multiple arguments allowed)
* @param {string|string[]} arg Language name (multiple arguments allowed)
* @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard/error stubs
**/
['addKeyboardsForLanguage'](arg) {
this.keyboardManager.addLanguageKeyboards(arguments);
['addKeyboardsForLanguage'](arg: string[]|string) : Promise<(com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[]> {
if (typeof arg === 'string') {
return this.keyboardManager.addLanguageKeyboards(arg.split(',').map(item => item.trim()));
} else {
return this.keyboardManager.addLanguageKeyboards(arg);
}
}
/**
@ -707,4 +728,4 @@ if(!window['keyman'] || !window['keyman']['loaded']) {
// TODO: Eliminate the need for this. Will require more refactoring & redesign to drop.
window['keyman'].core.languageProcessor.init();
})();
}
}

View file

@ -132,7 +132,7 @@ namespace com.keyman.text {
keymanweb.isEmbedded = true;
// Set default device options
keymanweb.setDefaultDeviceOptions = function(opt) {
keymanweb.setDefaultDeviceOptions = function(opt: com.keyman.OptionType) {
opt['attachType'] = 'manual';
device.app=opt['app'];
device.touchable=true;

View file

@ -26,11 +26,13 @@ if(!window['keyman']['initialized']) {
/**
* Set default device options
* @param {Object} opt device options object
* @param {OptionType} opt device options object
*/
keymanweb.setDefaultDeviceOptions=function(opt) {
keymanweb.setDefaultDeviceOptions = function(opt : com.keyman.OptionType) {
// Element attachment type
if(opt['attachType'] == '') opt['attachType'] = (device.touchable ? 'manual' : 'auto');
if (!opt['attachType']) {
opt['attachType'] = (device.touchable ? 'manual' : 'auto');
}
}
/**
@ -40,7 +42,7 @@ if(!window['keyman']['initialized']) {
*/
util.wait = function(s) {
// Keyboards loaded with page are initialized before the page is ready,
// so cannot use the wait indicater (and don't need it, anyway)
// so cannot use the wait indicator (and don't need it, anyway)
// Do not display if a blocking cloud server error has occurred (to prevent multiple errors)
var bg=this.waiting;
if(typeof(bg) == 'undefined' || bg == null || keymanweb.warned) {

View file

@ -667,7 +667,7 @@ namespace com.keyman {
}
if(eot != '' && (eot.indexOf('/') < 0)) {
eot = this.keyman.options['fonts']+eot;
eot = this.keyman.options['fonts']+eot;
}
if(svg != '' && (svg.indexOf('/') < 0)) {
@ -903,7 +903,7 @@ namespace com.keyman {
}
/**
* Customized alert
* Customized alert.
*
* @param {string} s alert text
* @param {function()=} fn function to call when alert dismissed
@ -915,11 +915,7 @@ namespace com.keyman {
(nn[1] as HTMLElement).innerHTML=s;
(nn[2] as HTMLElement).style.display='none';
bg.style.display='block';
if(arguments.length > 1) {
bg.dismiss=fn;
} else {
bg.dismiss=null;
}
bg.dismiss = arguments.length > 1 ? fn : null;
}
// Stub definition to be fleshed out depending upon native/embedded mode.
@ -927,6 +923,18 @@ namespace com.keyman {
}
/**
* Customized internal alert. This is enabled/disabled by the option flag 'useAlerts'
*
* @param {string} s alert text
* @param {function()=} fn function to call when alert dismissed
*/
internalAlert(s: string, fn?: () => void): void {
if (this.keyman.options.useAlerts) {
this.alert(s, fn);
}
}
/**
* Prepare the background and keyboard loading wait message box
* Should not be called before options are defined during initialization
@ -1146,4 +1154,4 @@ namespace com.keyman {
}
}
import Util = com.keyman.Util;
import Util = com.keyman.Util;

View file

@ -313,7 +313,11 @@ namespace com.keyman.osk {
source: text.MutableSystemStore,
newValue: string) {
if(source.value != newValue) {
this.vkbd.layerId = newValue;
// Prevents console errors when a keyboard only displays help.
// Can occur when using SHIFT with sil_euro_latin on a desktop form-factor.
if(this.vkbd) {
this.vkbd.layerId = newValue;
}
this._Show();
}
}.bind(this);
@ -479,8 +483,8 @@ namespace com.keyman.osk {
*/
showBuild() {
let keymanweb = com.keyman.singleton;
keymanweb.util.alert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'<br /><br />'
+'<span style="font-size:0.8em">Copyright &copy; 2017 SIL International</span>');
keymanweb.util.internalAlert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'<br /><br />'
+'<span style="font-size:0.8em">Copyright &copy; 2017 SIL International</span>');
}
/**

View file

@ -2,79 +2,68 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- Set the viewport width to match phone and tablet device widths -->
<meta name="viewport" content="width=device-width,user-scalable=no" />
<!-- Set the viewport width to match phone and tablet device widths -->
<meta name="viewport" content="width=device-width,user-scalable=no" />
<!-- Allow KeymanWeb to be saved to the iPhone home screen -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Enable IE9 Standards mode -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>KeymanWeb Sample Page - Uncompiled Source</title>
<!-- Your page CSS -->
<style type='text/css'>
<!-- Your page CSS -->
<style type='text/css'>
body {font-family: Tahoma,helvetica;}
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
#KeymanWebControl {width:50%;min-width:600px;}
</style>
<!-- Insert uncompiled KeymanWeb source scripts -->
#KeymanWebControl {width:50%;min-width:600px;}
</style>
<!-- Insert uncompiled KeymanWeb source scripts -->
<script src="../../release/unminified/web/keymanweb.js" type="application/javascript"></script>
<!--
<!--
For desktop browsers, a script for the user interface must be inserted here.
Standard UIs are toggle, button, float and toolbar.
The toolbar UI is best for any page designed to support keyboards for
Standard UIs are toggle, button, float and toolbar.
The toolbar UI is best for any page designed to support keyboards for
a large number of languages.
-->
<script src="../../release/unminified/web/kmwuitoggle.js"></script>
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
<script>
// Thank you to https://stackoverflow.com/questions/22607150/getting-the-url-parameters-inside-the-html-page.
var GetURLParameter = function(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
};
var attachType = GetURLParameter("mode");
var attachText = attachType ? attachType : "default";
var kmw=window.keyman;
kmw.init({
attachType: attachType ? attachType : '',
});
</script>
// Thank you to https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const attachType = (new URLSearchParams(window.location.search)).get("mode");
var attachText = attachType ? attachType : "default";
var kmw=window.keyman;
kmw.init({
attachType: attachType ? attachType : ''
});
</script>
<!-- Add keyboard management script for local selection of keyboards to use -->
<script src="../commonHeader.js"></script>
<script src="./utilities.js"></script>
<script src="./utilities.js"></script>
<script>
</script>
</head>
<!-- Sample page HTML -->
<!-- Sample page HTML -->
<body onload='loadKeyboards(1);'>
<h2>KeymanWeb Sample Page - Attachment API Testing</h2>
<p>This page is designed to stress-test the new attachment/enablement API model and its functions.</p>
<p id="modeSelect">You are currently testing under the <em id="mode"> </em> attachment mode. Two other modes are available:
<a id="attach_auto" href="./index.html?mode=auto">Auto</a> <a id="attach_manual" href="./index.html?mode=manual">Manual</a>
<p id="modeSelect">You are currently testing under the <em id="mode"> </em> attachment mode. Two other modes are available:
<a id="attach_auto" href="./index.html?mode=auto">Auto</a> <a id="attach_manual" href="./index.html?mode=manual">Manual</a>
<a id="attach_default" href="./index.html">Default</a><p>
<p>Be sure to reference the developer console for additional feedback. </p>
</p>Note that the 'attachment' state reported is managed by this page and not reported from KeymanWeb itself;
</p>Note that the 'attachment' state reported is managed by this page and not reported from KeymanWeb itself;
take it as a guide at best. (Especially if on a touch device.)</p>
<script>
document.getElementById("mode").textContent = attachText;
@ -88,21 +77,21 @@
<input type='button' id='btnEditable' onclick='addEditable();' value='Create editable DIV.' />
<hr/>
<div>
<!--
<!--
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
If omitted, those User Interfaces will appear at the top of the document body.
(It is ignored by other User Interfaces.)
-->
<div id='KeymanWebControl'></div>
<div id='KeymanWebControl'></div>
<div id='DynamicInputs'><h3>Inputs:</h3></div>
<div id='DynamicTextareas'><h3>Textareas:</h3></div>
<div id='DynamicIFrames'><h3>IFrames:</h3>
<p><em>Note:</em> The iframe section should not actually attach/enable for touch devices
<p><em>Note:</em> The iframe section should not actually attach/enable for touch devices
and does not support <code>setKeyboardForControl</code>.</p>
</div>
<div id='DynamicDesignFrames'><h3>Design-mode IFrames:</h3>
<p><em>Note:</em> The iframe section should not actually attach/enable for touch devices
<p><em>Note:</em> The iframe section should not actually attach/enable for touch devices
and does not support <code>setKeyboardForControl</code>.</p>
</div>
<div id='DynamicEditables'><h3><code>contenteditable</code> Elements:</h3>
@ -112,20 +101,20 @@
<hr/>
<h3><a href="../index.html">Return to testing home page</a></h3>
</body>
<!--
<!--
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
*
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
* to display the special characters used in the On-Screen Keyboard.
*
* To work around this Firefox bug, navigate to <b>about:config</b>
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
* while testing.
*
* to display the special characters used in the On-Screen Keyboard.
*
* To work around this Firefox bug, navigate to <b>about:config</b>
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
* while testing.
*
* Firefox resolves website-based CSS URI references correctly without needing
* any configuration change, so this change should only be made for file-based testing.
*
***
-->
*
***
-->
</html>

View file

@ -1,47 +1,47 @@
// JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages
/*
/*
The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available.
If the same keyboard is used for several languages, it must be listed for each
language, but the keyboard itself will only be loaded once.
language, but the keyboard itself will only be loaded once.
If two (or more) keyboards are to be available for a given language, both must be listed.
Any number of keyboards may be specified in one or more calls.
Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path.
Any number of keyboards may be specified in one or more calls.
Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path.
The actual keyboard object will be downloaded asynchronously when first selected for use.
Each argument to addKeyboards() is a string, for example:
european2 loads the current version of the Eurolatin 2 keyboard (for its default language)
european2@fr loads the current version of the Eurolatin 2 keyboard for French
european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French
Argument syntax also supports the following extensions:
@fr load the current version of the default keyboard for French
@fr$ load all available keyboards (current version) for French
Each call to addKeyboards() requires a single call to the remote server,
Each call to addKeyboards() requires a single call to the remote server,
(unless all keyboards listed are local and fully specified) so it is better
to use multiple arguments rather than separate function calls.
Calling addKeyboards() with no arguments returns a list of *all* available keyboards.
The Toolbar (desktop browser) UI is best suited for allowing users to select
to use multiple arguments rather than separate function calls.
Calling addKeyboards() with no arguments returns a list of *all* available keyboards.
The Toolbar (desktop browser) UI is best suited for allowing users to select
the appropriate language and keyboard in this case.
Keyboards may also be specified by language name using addKeyboardsForLanguage()
for example:
keymanweb.addKeyboardsForLanguage('Burmese');
Appending $ to the language name will again cause all available keyboards for that
language to be loaded rather than the default keyboard.
The first call to addKeyboardsForLanguage() makes an additional call to the
The first call to addKeyboardsForLanguage() makes an additional call to the
keyman API to load the current list of keyboard/language associations.
In this example, the following function loads the indicated keyboards,
and is called when the page loads.
and is called when the page loads.
*/
function loadKeyboards(nestLevel)
{
{
var kmw=keyman;
var base_prefix = '../';
@ -53,46 +53,46 @@
prefix = prefix + base_prefix;
}
}
// The first keyboard added will be the default keyboard for touch devices.
// For faster loading, it may be best for the default keyboard to be
// locally sourced.
kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
filename:(prefix + 'us-1.0.js')});
// Add more keyboards to the language menu, by keyboard name,
// keyboard name and language code, or just the BCP-47 language code.
// We use a different loading pattern here than in the samples version to provide a slightly different set of test cases.
kmw.addKeyboards('french','@he');
kmw.addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string.
// Add a keyboard by language name. Note that the name must be spelled
// correctly, or the keyboard will not be found. (Using BCP-47 codes is
// usually easier.)
kmw.addKeyboardsForLanguage('Dzongkha');
// Add a fully-specified, locally-sourced, keyboard with custom font
// Add a fully-specified, locally-sourced, keyboard with custom font
kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic',
languages: {
id:'lo',name:'Lao',region:'Asia',
},
filename:(prefix + 'lao_2008_basic-1.2.js')
});
});
// The following two optional calls should be delayed until language menus are fully loaded:
// (a) a specific mapped input element input is focused, to ensure that the OSK appears
// (b) a specific keyboard is loaded, rather than the keyboard last used.
// (b) a specific keyboard is loaded, rather than the keyboard last used.
//window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500);
//window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000);
// Note that locally specified keyboards will be listed before keyboards
// Note that locally specified keyboards will be listed before keyboards
// requested from the remote server by user interfaces that do not order
// keyboards alphabetically by language.
}
// Script to allow a user to add any keyboard to the keyboard menu
// Script to allow a user to add any keyboard to the keyboard menu
function addKeyboard(n)
{
{
var sKbd,kmw=keyman;
switch(n)
{
@ -105,16 +105,17 @@
kmw.addKeyboards('@'+sKbd);
break;
case 3:
// Add keyboard for comma-separated language name(s)
sKbd=document.getElementById('kbd_id3').value;
kmw.addKeyboardsForLanguage(sKbd);
break;
}
}
// Add keyboard on Enter (as well as pressing button)
function clickOnEnter(e,id)
{
e = e || window.event;
if(e.keyCode == 13) addKeyboard(id);
if(e.keyCode == 13) addKeyboard(id);
}

View file

@ -29,6 +29,7 @@
<h2><a href="./platform/">Platform testing</a></h2>
<h2><a href="./prediction-ui/">Prediction UI testing</a></h2>
<h2><a href="./prediction-mtnt/">Prediction - robust testing</a></h2>
<h2><a href="./promise-api/">Promise API testing</a></h2>
<h2><a href="./toolbar/">Toolbar UI testing</a></h2>
<h2><a href="./build-visual-keyboard/">Tests keyboard documentation rendering.</a></h2>
<h2><a href="./sentry-integration/">Tests Sentry error-reporting functionality</a></h2>

View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- Set the viewport width to match phone and tablet device widths -->
<meta name="viewport" content="width=device-width,user-scalable=no" />
<!-- Allow KeymanWeb to be saved to the iPhone home screen -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Enable IE9 Standards mode -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>KeymanWeb Testing Page - Promise API when Adding Keyboards</title>
<!-- Your page CSS -->
<style type='text/css'>
body {font-family: Tahoma,helvetica;}
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
#KeymanWebControl {width:50%;min-width:600px;}
</style>
<!-- Insert uncompiled KeymanWeb source scripts -->
<script src="../../release/unminified/web/keymanweb.js" type="application/javascript"></script>
<!--
For desktop browsers, a script for the user interface must be inserted here.
Standard UIs are toggle, button, float and toolbar.
The toolbar UI is best for any page designed to support keyboards for
a large number of languages.
-->
<script src="../../release/unminified/web/kmwuitoggle.js"></script>
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
<script>
// Thank you to https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const alertType = (new URLSearchParams(window.location.search)).get("useAlerts") != "false";
var alertText = alertType ? 'enabled(default)' : 'disabled';
var kmw=window.keyman;
kmw.init({
useAlerts: alertType
}).then(function() {
loadKeyboards();
});
</script>
<!-- Add keyboard management script for local selection of keyboards to use -->
<script src="promisehdr.js"></script>
</head>
<!-- Sample page HTML -->
<body>
<h2>KeymanWeb Sample Page - Promise API</h2>
<p>This page is for testing the Promises returned from adding keyboards.
See <a href="https://github.com/keymanapp/keyman/issues/5044">Specification #5044.</a>
</p>
<p>
This test page is best used with Developer mode, as output for both successes
and failures will be logged to the JS console.
</p>
<p>Visible alert-style feedback will only occur for invalid search terms, such as using "foobar"
for any of the three keyboard-adding options at the bottom of the page.
</p>
<p id="modeSelect">You are currently testing under the internal alerts <em id="mode"> </em> mode.
Two alert modes are available:
<a id="alert_disabled" href="./index.html?useAlerts=false">Disabled</a> or
<a id="alert_default" href="./index.html">Enabled (Default)</a></p>
<p>This will only affect whether or not KeymanWeb's alert modal is displayed.
A native browser alert and dev-console error log will occur regardless of this
setting when stub fetch errors occur.
</p>
<script>
document.getElementById("mode").textContent = alertText;
</script>
<div>
<!--
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
If omitted, those User Interfaces will appear at the top of the document body.
(It is ignored by other User Interfaces.)
-->
<div id='KeymanWebControl'></div>
<h3>Type in your language in this text area:</h3>
<textarea id='ta1' class='test' placeholder='Type here'></textarea>
<h3>or in this input field:</h3>
<input class='test' value='' placeholder='or here'/>
<!-- The following elements show how the language menu can be dynamically extended at any time -->
<h3>Add a keyboard by keyboard name:</h3>
<input type='input' id='kbd_id1' class='kmw-disabled' onkeypress="clickOnEnter(event,1);"/>
<input type='button' id='btn1' onclick='addKeyboard(1);' value='Add' />
<h3>Add a keyboard by BCP-47 language code:</h3>
<input type='input' id='kbd_id2' class='kmw-disabled' onkeypress="clickOnEnter(event,2);"/>
<input type='button' id='btn2' onclick='addKeyboard(2);' value='Add' />
<h3>Add a keyboard by comma-separated language name(s):</h3>
<input type='input' id='kbd_id3' class='kmw-disabled' onkeypress="clickOnEnter(event,3);"/>
<input type='button' id='btn3' onclick='addKeyboard(3);' value='Add' />
<h3><a href="./">Return to testing home page</a></h3>
</div>
</body>
<!--
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
*
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
* to display the special characters used in the On-Screen Keyboard.
*
* To work around this Firefox bug, navigate to <b>about:config</b>
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
* while testing.
*
* Firefox resolves website-based CSS URI references correctly without needing
* any configuration change, so this change should only be made for file-based testing.
*
***
-->
</html>

View file

@ -0,0 +1,130 @@
// JavaScript Document promisehdr.js: Keyboard management for KeymanWeb demonstration pages
/*
The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available.
If the same keyboard is used for several languages, it must be listed for each
language, but the keyboard itself will only be loaded once.
If two (or more) keyboards are to be available for a given language, both must be listed.
Any number of keyboards may be specified in one or more calls.
Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path.
The actual keyboard object will be downloaded asynchronously when first selected for use.
Each argument to addKeyboards() is a string, for example:
european2 loads the current version of the Eurolatin 2 keyboard (for its default language)
european2@fr loads the current version of the Eurolatin 2 keyboard for French
european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French
Argument syntax also supports the following extensions:
@fr load the current version of the default keyboard for French
@fr$ load all available keyboards (current version) for French
Each call to addKeyboards() requires a single call to the remote server,
(unless all keyboards listed are local and fully specified) so it is better
to use multiple arguments rather than separate function calls.
Calling addKeyboards() with no arguments returns a list of *all* available keyboards.
The Toolbar (desktop browser) UI is best suited for allowing users to select
the appropriate language and keyboard in this case.
Keyboards may also be specified by language name using addKeyboardsForLanguage()
for example:
keymanweb.addKeyboardsForLanguage('Burmese');
Appending $ to the language name will again cause all available keyboards for that
language to be loaded rather than the default keyboard.
The first call to addKeyboardsForLanguage() makes an additional call to the
keyman API to load the current list of keyboard/language associations.
In this example, the following function loads the indicated keyboards,
and is called when the page loads.
*/
/**
* Add a keyboard by language name. Note that the name must be spelled
correctly, or the keyboard will not be found. (Using BCP-47 codes is
usually easier.)
* @param {string} languages. Could be comma-separated languages
*/
function addKeyboardsForLanguage(languages) {
kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => {
console.log('Adding ' + languages + ': ', result);
}).catch(errorStubs => {
// Consumer decides how to handle errors
errorStubs.forEach(e => console.error(e));
// We'll also concat the error messages to an alert
let errorMessages = errorStubs.map(e => e.error.message).join('\n');
alert(errorMessages);
});
}
function loadKeyboards()
{
var kmw=keyman;
// The first keyboard added will be the default keyboard for touch devices.
// For faster loading, it may be best for the default keyboard to be
// locally sourced.
kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
filename:'../us-1.0.js'});
// Add more keyboards to the language menu, by keyboard name,
// keyboard name and language code, or just the BCP-47 language code.
// We use a different loading pattern here than in the samples version to provide a slightly different set of test cases.
kmw.addKeyboards('french','@he');
kmw.addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string.
// Add a keyboard by language name. Note that the name must be spelled
// correctly, or the keyboard will not be found. (Using BCP-47 codes is
// usually easier.)
addKeyboardsForLanguage('Dzongkha');
// Add a fully-specified, locally-sourced, keyboard with custom font
kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic',
languages: {
id:'lo',name:'Lao',region:'Asia'
},
filename:'../lao_2008_basic-1.2.js'
});
// The following two optional calls should be delayed until language menus are fully loaded:
// (a) a specific mapped input element input is focused, to ensure that the OSK appears
// (b) a specific keyboard is loaded, rather than the keyboard last used.
//window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500);
//window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000);
// Note that locally specified keyboards will be listed before keyboards
// requested from the remote server by user interfaces that do not order
// keyboards alphabetically by language.
}
// Script to allow a user to add any keyboard to the keyboard menu
function addKeyboard(n)
{
var sKbd,kmw=keyman;
switch(n)
{
case 1:
sKbd=document.getElementById('kbd_id1').value;
kmw.addKeyboards(sKbd);
break;
case 2:
sKbd=document.getElementById('kbd_id2').value.toLowerCase();
kmw.addKeyboards('@'+sKbd);
break;
case 3:
// Add keyboard for comma-separated language name(s)
sKbd=document.getElementById('kbd_id3').value;
addKeyboardsForLanguage(sKbd);
break;
}
}
// Add keyboard on Enter (as well as pressing button)
function clickOnEnter(e,id)
{
e = e || window.event;
if(e.keyCode == 13) addKeyboard(id);
}

Some files were not shown because too many files have changed in this diff Show more