diff --git a/HISTORY.md b/HISTORY.md index 5803417172..008f986960 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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) diff --git a/VERSION.md b/VERSION.md index 8a872f0ae5..adb90c6b70 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.74 \ No newline at end of file +15.0.79 \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 3d6b75aec5..3d2aee8054 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -41,7 +41,7 @@ ta.readOnly = false; // Tell KMW the default banner height to use - com.keyman.osk.Banner.DEFAULT_HEIGHT = + com.keyman.osk.Banner.DEFAULT_HEIGHT = Math.ceil(window.jsInterface.getDefaultBannerHeight() / window.devicePixelRatio); kmw.addEventListener('keyboardloaded', setIsChiral); @@ -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 @@ -
- -
+ diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java index 175fd6e2ac..a2eb5d2047 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java @@ -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") diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java index 15f9338daa..2e6a96c893 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java @@ -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"; diff --git a/android/README.md b/android/README.md index 89cf55db67..c7dd245af8 100644 --- a/android/README.md +++ b/android/README.md @@ -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** diff --git a/android/help/about/whatsnew.md b/android/help/about/whatsnew.md index d89659492b..d585fb3552 100644 --- a/android/help/about/whatsnew.md +++ b/android/help/about/whatsnew.md @@ -4,6 +4,7 @@ title: What's New Here are some of the new features we have added to Keyman 15.0 for Android: * Meet Google Play's target API level requirement of 30 (Android 11) +* Add a settings option to change the displayed keyboard name on the spacebar [![](../android_images/video.png) Watch a video](https://youtu.be/ehk0Bts28JE) that highlights some of these new features. diff --git a/android/help/android_images/settings-screen-ap.png b/android/help/android_images/settings-screen-ap.png index 6255404335..cb88655c70 100644 Binary files a/android/help/android_images/settings-screen-ap.png and b/android/help/android_images/settings-screen-ap.png differ diff --git a/android/help/android_images/spacebar-caption-ap.png b/android/help/android_images/spacebar-caption-ap.png new file mode 100644 index 0000000000..47ae3ab385 Binary files /dev/null and b/android/help/android_images/spacebar-caption-ap.png differ diff --git a/android/help/basic/config/index.md b/android/help/basic/config/index.md index 1e8dd29db8..8d797d662b 100644 --- a/android/help/basic/config/index.md +++ b/android/help/basic/config/index.md @@ -38,6 +38,20 @@ For installed keyboard packages, click on this to associate another language wit If all the languages for a keyboard have already been installed, this won't change anything. +## Change Display Language +![](../../android_images/ic_translate_a.png) + +Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale. + +## Spacebar Caption +Click on this to change the displayed keyboard name on the spacebar. The options are: +* Language name +* Keyboard name +* Language name + Keyboard name +* Blank + +![](../../android_images/spacebar-caption-ap.png) + ## Enable Keyman as system-wide keyboard By default, installed Keyman keyboards are only available to use within the Keyman app. Click on this setting to bring up the Android system configuration so Keyman can be added as a system-wide keyboard. @@ -45,11 +59,6 @@ bring up the Android system configuration so Keyman can be added as a system-wid ## Set Keyman as default keyboard Click on this to bring up the Android system configuration for setting Keyman as the default system-wide keyboard. -## Change Display Language -![](../../android_images/ic_translate_a.png) - -Click on this to change the display language of the Keyman for Android app. The list of available languages come from [translate.keyman.com](https://translate.keyman.com). If a translation is available, Keyman for Android will default to the device's default locale. - ## Always show banner This toggle is reserved for future features. When off, the text suggestion banner is only displayed when the dictionary is enabled. The language associated with the keyboard must match the language associated with diff --git a/common/core/desktop/doc/BUILDING.md b/common/core/desktop/doc/BUILDING.md index 6573a32914..db55f6aca0 100644 --- a/common/core/desktop/doc/BUILDING.md +++ b/common/core/desktop/doc/BUILDING.md @@ -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 (or `cargo` package on Linux) - -For WASM builds: -- Meson build system 0.56+ -- emscripten 2.0.23+ -- WasmPack 0.9.1+ -- ninja 1.10+ for WASM - -### Windows - -* Install Git for Windows: - -* Install Python 3: - - You can get the official Python installer from the official Python site: - - - 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: - - - Run the command: - - ```bash - rustup target add i686-pc-windows-msvc - rustup target add wasm32-unknown-unknown - ``` - -* Install wasm-pack: - - -* Install Emscripten: - - -* 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): - - -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 , 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): - - -* 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: - - -* 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): - - -#### 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 diff --git a/common/core/desktop/src/kmx/kmx_context.cpp b/common/core/desktop/src/kmx/kmx_context.cpp index ffea32be4c..6efa11f94f 100644 --- a/common/core/desktop/src/kmx/kmx_context.cpp +++ b/common/core/desktop/src/kmx/kmx_context.cpp @@ -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; } diff --git a/common/core/desktop/src/kmx/kmx_context.h b/common/core/desktop/src/kmx/kmx_context.h index 8e3664d82f..111d08104c 100644 --- a/common/core/desktop/src/kmx/kmx_context.h +++ b/common/core/desktop/src/kmx/kmx_context.h @@ -21,15 +21,89 @@ private: public: KMX_Context(); + + /** + * @brief Copy "source" AppContext to this AppContext + * + * @param source KMX_Context to copy + */ void CopyFrom(KMX_Context *source); + + /** + * @brief Add a single code unit to the Current Context. Not necessarily a complete code point + * + * @param Code unit to add + */ void Add(KMX_WCHAR ch); + + /** + * @brief Removes a single code point from the end of the CurContext closest to the caret; + * i.e. it will be both code units if a surrogate pair. If it is a deadkey it will + * remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value. + */ void Delete(); + + /** + * @brief Clears the CurContext and resets the position - pos - index + */ void Reset(); + + /** + * @brief Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize The number of code units ie size of the KMX_WCHAR buffer - not the code points + */ void Get(KMX_WCHAR *buf, int bufsize); + + /** + * @brief Sets the CurContext to the supplied buf character array and updates the pos index. + * + * @param buf + */ void Set(const KMX_WCHAR *buf); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n valid xstring units remaining until the null terminating + * character. It will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair or deadkey. + * + * @param n The maximum number of valid xstring units (not code points or code units) + * @return 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(); }; diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 70b2db0e37..cdb898d011 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index cfaa8c6aab..772cffb71b 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.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" } } diff --git a/common/core/web/input-processor/tests/cases/inputProcessor.js b/common/core/web/input-processor/tests/cases/inputProcessor.js index 3344949a0f..ef4d7e364b 100644 --- a/common/core/web/input-processor/tests/cases/inputProcessor.js +++ b/common/core/web/input-processor/tests/cases/inputProcessor.js @@ -103,7 +103,9 @@ describe('InputProcessor', function() { // Assumes no SMP chars in the source, which is fine. let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength()); - this.timeout(250); // 250 ms, excluding text import. + this.timeout(500); // 500 ms, excluding text import. + // These often run on VMs, so we'll be a bit generous. + let core = new InputProcessor(); // I mean, it IS long context, and time // thresholding is disabled within Node. @@ -137,8 +139,13 @@ describe('InputProcessor', function() { // Assumes no SMP chars in the source, which is fine. let context = new com.keyman.text.Mock(coreSourceCode, coreSourceCode._kmwLength()); - this.timeout(250); // 250 ms, excluding text import. - let core = new InputProcessor(); // I mean, it IS long context, and time + this.timeout(500); // 500 ms, excluding text import. + // These often run on VMs, so we'll be a bit generous. + // + // Keep at the same 'order of magnitude' as the + // 'without fat-fingers' test. + + let core = new InputProcessor(); // It IS long context, and time // thresholding is disabled within Node. core.activeKeyboard = keyboard; diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 67bb79dc09..896fdff38b 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 9a16643887..c720b3bcb1 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.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" } } diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index 159b0a5207..4126c265b2 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 097741f4a1..bb0ea50430 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.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" diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 40c7fe85ae..d3fedcac22 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index 8759e651d4..8d4c4ae07e 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.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" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index ead1b8b557..973839ab33 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index dba9b46df2..56ba82dfcd 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.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" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 9a4707aa72..a9f880510e 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index c05c906aba..8fbd68b6ea 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.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" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index 25357704b4..021d3041de 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index 696cc652c1..772f892ccb 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.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": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index 0f00f148a3..a3e130991b 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 096c5fd886..3ca42e32fd 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.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", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 1439ab5785..609c6703f1 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index 933bd9ca36..c8589e8520 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.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" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index 3043776b92..05116874f9 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index d92aabdcbd..8981db6150 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.74", + "version": "15.0.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", diff --git a/docs/build/index.md b/docs/build/index.md new file mode 100644 index 0000000000..bca0627234 --- /dev/null +++ b/docs/build/index.md @@ -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. diff --git a/docs/build/linux-ubuntu.md b/docs/build/linux-ubuntu.md new file mode 100644 index 0000000000..910a3b8d2d --- /dev/null +++ b/docs/build/linux-ubuntu.md @@ -0,0 +1,3 @@ +# Setup your Keyman build environment on Ubuntu + +TODO diff --git a/docs/build/macos.md b/docs/build/macos.md new file mode 100644 index 0000000000..41a45f7ad2 --- /dev/null +++ b/docs/build/macos.md @@ -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. diff --git a/docs/build/old/android-notes.md b/docs/build/old/android-notes.md new file mode 100644 index 0000000000..409f0057b2 --- /dev/null +++ b/docs/build/old/android-notes.md @@ -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 diff --git a/docs/build/old/core-desktop-notes.md b/docs/build/old/core-desktop-notes.md new file mode 100644 index 0000000000..4eceb94fd0 --- /dev/null +++ b/docs/build/old/core-desktop-notes.md @@ -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 (or `cargo` package on Linux) + +For WASM builds: +- Meson build system 0.56+ +- emscripten 2.0.23+ +- WasmPack 0.9.1+ +- ninja 1.10+ for WASM + +### Windows + +* Install Git for Windows: + +* Install Python 3: + + You can get the official Python installer from the official Python site: + + + 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: + + + Run the command: + + ```bash + rustup target add i686-pc-windows-msvc + rustup target add wasm32-unknown-unknown + ``` + +* Install wasm-pack: + + +* Install Emscripten: + + +* 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): + + +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 , 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): + + +* 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: + + +* 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): + + +#### 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 + ``` \ No newline at end of file diff --git a/docs/build/old/ios-notes.md b/docs/build/old/ios-notes.md new file mode 100644 index 0000000000..47fd967f0e --- /dev/null +++ b/docs/build/old/ios-notes.md @@ -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. diff --git a/docs/build/old/macos-notes.md b/docs/build/old/macos-notes.md new file mode 100644 index 0000000000..77a83bd038 --- /dev/null +++ b/docs/build/old/macos-notes.md @@ -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/ diff --git a/docs/build/old/web-notes.md b/docs/build/old/web-notes.md new file mode 100644 index 0000000000..23ebf26d61 --- /dev/null +++ b/docs/build/old/web-notes.md @@ -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 +``` diff --git a/docs/build/sentry-cli.md b/docs/build/sentry-cli.md new file mode 100644 index 0000000000..7136bee2c1 --- /dev/null +++ b/docs/build/sentry-cli.md @@ -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`. diff --git a/docs/build/windows.md b/docs/build/windows.md new file mode 100644 index 0000000000..f0f48e0733 --- /dev/null +++ b/docs/build/windows.md @@ -0,0 +1,3 @@ +# Setup your Keyman build environment on Windows + +TODO diff --git a/ios/README.md b/ios/README.md index e79d99884f..49c3d57233 100644 --- a/ios/README.md +++ b/ios/README.md @@ -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. \ No newline at end of file diff --git a/lerna.json b/lerna.json index 6d03b53a43..a024675af1 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.74" + "version": "15.0.79" } diff --git a/linux/.pbuilderrc b/linux/.pbuilderrc index e02ef13492..7eb25cb256 100644 --- a/linux/.pbuilderrc +++ b/linux/.pbuilderrc @@ -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" diff --git a/linux/Jenkinsfile b/linux/Jenkinsfile index 7eedddcf2d..c347d391ed 100644 --- a/linux/Jenkinsfile +++ b/linux/Jenkinsfile @@ -5,6 +5,6 @@ @Library('lsdev-pipeline-library') _ keymanPackaging { - distributionsToPackage = 'bionic focal groovy hirsute' + distributionsToPackage = 'bionic focal groovy hirsute impish' arches = 'amd64 i386' } diff --git a/linux/help/about/history.md b/linux/help/about/history.md index 506782a42f..6343e0313e 100644 --- a/linux/help/about/history.md +++ b/linux/help/about/history.md @@ -5,4 +5,5 @@ title: Version History A frequent updated changelog for Keyman is kept at [help.keyman.com/version-history](https://help.keyman.com/version-history) on the Keyman Help website. ## Related Topics + * [What's New](whatsnew) diff --git a/linux/help/about/index.md b/linux/help/about/index.md index dbc704bbf4..5064ed6a09 100644 --- a/linux/help/about/index.md +++ b/linux/help/about/index.md @@ -4,4 +4,4 @@ title: About Keyman * [Welcome to Keyman](welcome) * [What's New](whatsnew) -* [Version History](history) \ No newline at end of file +* [Version History](history) diff --git a/linux/help/about/welcome.md b/linux/help/about/welcome.md index 0608c4d10c..ecab5b7b29 100644 --- a/linux/help/about/welcome.md +++ b/linux/help/about/welcome.md @@ -5,8 +5,9 @@ title: Welcome to Keyman Thank you for installing Keyman for Linux. Whether you're a new or returning user, we believe you will appreciate the significant enhancements in this latest version of Keyman for Linux. ## What is Keyman for Linux? + Keyman for Linux makes it possible to type in over 1,000 languages in any Linux application. Keyman for Linux also supports KMFL keyboards, so your existing KMFL keyboards will continue to work. -[Click here](../start/) to get started. \ No newline at end of file +[Click here](../start/) to get started. diff --git a/linux/help/about/whatsnew.md b/linux/help/about/whatsnew.md index b4d235c018..0edd3e50ec 100644 --- a/linux/help/about/whatsnew.md +++ b/linux/help/about/whatsnew.md @@ -9,5 +9,5 @@ Here are some of the new features we have added to Keyman for Linux 14.0: * Improved user interface * Improved support for KDE, Gnome, Arch Linux -[![](../linux_images/video.png) Watch a video](https://youtu.be/4W9Z9_IFUII) +[![Watch a video](../linux_images/video.png)](https://youtu.be/4W9Z9_IFUII) that highlights some of these new features. diff --git a/linux/help/common/index.md b/linux/help/common/index.md index 9d8fd517c5..4d4f39ba05 100644 --- a/linux/help/common/index.md +++ b/linux/help/common/index.md @@ -3,7 +3,9 @@ title: Common Questions --- ## Q. How do I install Keyman for Linux? + **A.** In Ubuntu and Wasta-Linux, Keyman for Linux can be installed via launchpad: + ```bash sudo add-apt-repository ppa:keymanapp/keyman sudo apt-get update @@ -11,22 +13,25 @@ sudo apt-get upgrade sudo apt-get install keyman onboard-keyman ``` -An older version of Keyman for Linux packages is also available in any current Debian at: +An older version of Keyman for Linux packages is also available in any current Debian at: [https://packages.debian.org/keyman](https://packages.debian.org/keyman) - ## Q. How do I install a Keyman keyboard? + **A.** Follow this [guide](../start/installing-keyboard) for installing Keyman keyboards. ## Q. What's the relationship between Keyman for Linux and KMFL? + **A.** Keyman for Linux and KMFL are released together in the Keyman [PPA](https://en.wikipedia.org/wiki/Ubuntu#Package_Archives), but are separate. The installation instructions above will install Keyman, not KMFL. KMFL was developed as a black-box clone of Keyman -for Windows years ago; after Keyman was released as open source it became possible to share the +for Windows years ago; after Keyman was released as open source it became possible to share the code base between Windows and Linux. Keyman for Linux is the long-term supported solution. ## Q. Can I have Keyman for Linux and KMFL installed at the same time? + **A.** Yes. To install KMFL on Ubuntu: + ```bash sudo add-apt-repository ppa:keymanapp/keyman sudo apt-get update @@ -35,22 +40,27 @@ sudo apt-get install ibus-kmfl ``` ## Q. If I already had KMFL installed, how can I uninstall KMFL before installing Keyman? + **A.** It is good to remove any keyboards from ibus e.g. KMFL keyboards before you remove KMFL. Then, to remove KMFL: + ```bash sudo dpkg --purge ibus-kmfl libkmfl ``` ## Q. How do I display the On-screen Keyboard (OSK)? + **A.** From the launcher, enter `onboard` ![](../linux_images/onboard.png "Onboard") ## Q. What Linux distros will Keyman work with? + **A.** Keyman runs on Debian, Ubuntu, Wasta Linux and can be compiled to run from source in most distributions. **Note:** Keyman for Linux no longer supports Ubuntu 16.04 LTS (Xenial Xerus). ## Q. Will my existing Windows Keyman keyboard work with Keyman for Linux? + **A.** Most keyboards will work without change. A small subset of keyboards require features which are not yet available in Keyman for Linux. These features will be progressively implemented. diff --git a/linux/help/index.md b/linux/help/index.md index 5e52e8fe40..0abc777916 100644 --- a/linux/help/index.md +++ b/linux/help/index.md @@ -1,20 +1,23 @@ --- -title: Keyman for Linux 14.0 Help +title: Keyman for Linux 15.0 Help --- Need help using Keyman for Linux? In time, this product documentation will grow and explain frequently asked questions. ## [About Keyman for Linux](about/) + * [Welcome to Keyman](about/welcome) * [What's New](about/whatsnew) * [Version History](about/history) ## [Getting Started](start/) + * [Installing a keyboard](start/installing-keyboard) ## [Common Questions](common/) ## Ask for Help + [Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman) ## Help Topics diff --git a/linux/help/reference/index.md b/linux/help/reference/index.md new file mode 100644 index 0000000000..2c76ebfe36 --- /dev/null +++ b/linux/help/reference/index.md @@ -0,0 +1,25 @@ +--- +title: Keyman 15.0 for Linux Reference +--- + +## Keyman 15.0 for Linux Overview + +Keyman for Linux makes it possible for you to type in over 1,000 languages on Linux. + +[km-config](km-config) \ +Keyman keyboards installation and information + +[km-kvk2ldml](km-kvk2ldml) \ +Convert a Keyman on-screen keyboard file to LDML + +[km-package-get](km-package-get) \ +Download a Keyman keyboard package + +[km-package-install](km-package-install) \ +Install a Keyman keyboard package + +[km-package-list-installed](km-package-list-installed) \ +List installed Keyman keyboard packages + +[km-package-uninstall](km-package-uninstall) \ +Uninstall a Keyman keyboard package diff --git a/linux/help/start/index.md b/linux/help/start/index.md index 2468dafa5a..b3aad00bd5 100644 --- a/linux/help/start/index.md +++ b/linux/help/start/index.md @@ -1,5 +1,5 @@ --- -title: Getting Started" +title: Getting Started --- * [How To - Installing Keyboards with Keyman for Linux](installing-keyboard) diff --git a/linux/help/start/installing-keyboard.md b/linux/help/start/installing-keyboard.md index f571d6f8b8..42e2ecbbaa 100644 --- a/linux/help/start/installing-keyboard.md +++ b/linux/help/start/installing-keyboard.md @@ -8,6 +8,7 @@ Alternatively from the command line run `km-config`. ![](../linux_images/km-config.png "Keyman Configuration") ## Download from Keyman cloud repository + From Keyman Configuration, click "Download". At the search page, enter the keyboard you want to download. ![](../linux_images/search-khmer-angkor.png "Search for khmer_angkor keyboard") @@ -17,6 +18,7 @@ Clicking the keyboard brings the keyboard page which has a link for installation ![](../linux_images/download-khmer-angkor.png "Install khmer_angkor") ## Install Local .kmp keyboard package + Keyman for Linux can also install keyboards from local .kmp keyboard packages. From Keyman Configuration, click "Install". Then browse to the local .kmp keyboard package and install. diff --git a/linux/keyman-config/debian/changelog b/linux/keyman-config/debian/changelog index 763386f9b8..7e469baba7 100644 --- a/linux/keyman-config/debian/changelog +++ b/linux/keyman-config/debian/changelog @@ -1,3 +1,10 @@ +keyman-config (14.0.276-2) experimental; urgency=medium + + * Team upload + * Add libglib2.0-bin to keyman Depends (closes: #990226) + + -- Gunnar Hjalmarsson Wed, 23 Jun 2021 15:28:27 +0200 + keyman-config (14.0.276-1) experimental; urgency=medium * New upstream release diff --git a/linux/keyman-config/debian/control b/linux/keyman-config/debian/control index 2e844fc96b..4368565fe0 100644 --- a/linux/keyman-config/debian/control +++ b/linux/keyman-config/debian/control @@ -34,6 +34,7 @@ Homepage: https://www.keyman.com Package: keyman Architecture: all Depends: + libglib2.0-bin, python3-keyman-config, ${misc:Depends}, Recommends: diff --git a/linux/keyman-config/debian/keyman.bash-completion b/linux/keyman-config/debian/keyman.bash-completion index cce485f8a4..bd000c8efd 100644 --- a/linux/keyman-config/debian/keyman.bash-completion +++ b/linux/keyman-config/debian/keyman.bash-completion @@ -1,3 +1,4 @@ +km-config.bash-completion km-config km-kvk2ldml.bash-completion km-kvk2ldml km-package-get.bash-completion km-package-get km-package-install.bash-completion km-package-install diff --git a/linux/keyman-config/keyman_config/deprecated_decorator.py b/linux/keyman-config/keyman_config/deprecated_decorator.py new file mode 100644 index 0000000000..9b9e1383a1 --- /dev/null +++ b/linux/keyman-config/keyman_config/deprecated_decorator.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +# based on https://stackoverflow.com/a/40301488 +import logging + + +string_types = (type(b''), type(u'')) + + +def deprecated(reason): + if isinstance(reason, string_types): + # The @deprecated is used with a 'reason'. + def decorator(func1): + def new_func1(*args, **kwargs): + logging.warning("Call to deprecated function '{name}': {reason}.".format( + name=func1.__name__, reason=reason)) + return func1(*args, **kwargs) + return new_func1 + return decorator + else: + # The @deprecated is used without any 'reason'. + def new_func2(*args, **kwargs): + func2 = reason + logging.warning("Call to deprecated function '{name}'.".format(name=func2.__name__)) + return func2(*args, **kwargs) + return new_func2 diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index bf5eb00a32..9d5665aaa1 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -6,7 +6,16 @@ import logging import requests import requests_cache import os +from gi.repository import GObject from keyman_config import KeymanApiUrl, KeymanDownloadsUrl +from keyman_config.deprecated_decorator import deprecated + + +class InstallLocation(GObject.GEnum): + OS = 1 + Shared = 2 + User = 3 + Unknown = 99 def get_package_download_data(packageID, weekCache=False): @@ -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 diff --git a/linux/keyman-config/keyman_config/ibus_util.py b/linux/keyman-config/keyman_config/ibus_util.py index ae25ec5212..464ea116bd 100644 --- a/linux/keyman-config/keyman_config/ibus_util.py +++ b/linux/keyman-config/keyman_config/ibus_util.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 +import os import time import gi import logging @@ -68,15 +69,21 @@ def restart_ibus_subp(): def restart_ibus(bus=None): - try: - if not bus: - bus = get_ibus_bus() - logging.info("restarting IBus") - bus.exit(True) - bus.destroy() - except Exception as e: - logging.warning("Failed to restart IBus") - logging.warning(e) + realuser = os.environ.get('SUDO_USER') + if realuser: + # we have been called with `sudo`. Restart ibus for the real user. + logging.info('restarting IBus by subprocess for user %s', realuser) + subprocess.run(['sudo', '-u', realuser, 'ibus', 'restart']) + else: + try: + if not bus: + bus = get_ibus_bus() + logging.info("restarting IBus") + bus.exit(True) + bus.destroy() + except Exception as e: + logging.warning("Failed to restart IBus") + logging.warning(e) def bus_has_engine(bus, name): diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index aa36bfed60..172fec5909 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -10,7 +10,8 @@ from enum import Enum from keyman_config import _ from keyman_config.canonical_language_code_utils import CanonicalLanguageCodeUtils from keyman_config.fcitx_util import is_fcitx_running, restart_fcitx -from keyman_config.get_kmp import get_keyboard_data, user_keyboard_dir, user_keyman_font_dir +from keyman_config.get_kmp import get_keyboard_data, get_keyboard_dir, get_keyman_doc_dir +from keyman_config.get_kmp import get_keyman_font_dir, InstallLocation from keyman_config.kmpmetadata import get_metadata, KMFileTypes from keyman_config.convertico import extractico, checkandsaveico from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml @@ -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 '' diff --git a/linux/keyman-config/keyman_config/install_window.py b/linux/keyman-config/keyman_config/install_window.py index 820253b6c0..76482b022c 100755 --- a/linux/keyman-config/keyman_config/install_window.py +++ b/linux/keyman-config/keyman_config/install_window.py @@ -11,26 +11,26 @@ import webbrowser import tempfile import gi -from keyman_config.fcitx_util import is_fcitx_running gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') from gi.repository import Gtk, WebKit2 from distutils.version import StrictVersion from keyman_config import _ +from keyman_config.fcitx_util import is_fcitx_running from keyman_config.install_kmp import install_kmp, extract_kmp, get_metadata, InstallError, InstallStatus from keyman_config.list_installed_kmp import get_kmp_version from keyman_config.kmpmetadata import get_fonts from keyman_config.welcome import WelcomeView from keyman_config.uninstall_kmp import uninstall_kmp -from keyman_config.get_kmp import user_keyboard_dir +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir from keyman_config.accelerators import bind_accelerator, init_accel def find_keyman_image(image_file): - img_path = os.path.join("/usr/share/keyman/icons", image_file) + img_path = os.path.join(get_keyman_dir(InstallLocation.OS), "icons", image_file) if not os.path.isfile(img_path): - img_path = os.path.join("/usr/local/share/keyman/icons/", image_file) + img_path = os.path.join(get_keyman_dir(InstallLocation.Shared), "icons", image_file) if not os.path.isfile(img_path): img_path = os.path.join("keyman_config/icons/", image_file) if not os.path.isfile(img_path): @@ -302,7 +302,7 @@ class InstallKmpWindow(Gtk.Dialog): result = install_kmp(self.kmpfile, self.online, language=self.language) if result: # If install_kmp returns a string, it is an instruction for the end user, - # because for fcitx they will need to take extra steps to complete + # because for fcitx they will need to take extra steps to complete # installation themselves. dialog = Gtk.MessageDialog( self, 0, Gtk.MessageType.INFO, @@ -315,7 +315,8 @@ class InstallKmpWindow(Gtk.Dialog): if self.viewwindow: self.viewwindow.refresh_installed_kmp() keyboardid = os.path.basename(os.path.splitext(self.kmpfile)[0]) - welcome_file = os.path.join(user_keyboard_dir(keyboardid), "welcome.htm") + welcome_file = os.path.join(get_keyboard_dir(InstallLocation.User, keyboardid), + "welcome.htm") if os.path.isfile(welcome_file): uri_path = pathlib.Path(welcome_file).as_uri() logging.debug(uri_path) diff --git a/linux/keyman-config/keyman_config/keyboard_details.py b/linux/keyman-config/keyman_config/keyboard_details.py index 24945cb8f5..1c7d4f1253 100644 --- a/linux/keyman-config/keyman_config/keyboard_details.py +++ b/linux/keyman-config/keyman_config/keyboard_details.py @@ -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, diff --git a/linux/keyman-config/keyman_config/list_installed_kmp.py b/linux/keyman-config/keyman_config/list_installed_kmp.py index 12177a5e60..1745a10dba 100755 --- a/linux/keyman-config/keyman_config/list_installed_kmp.py +++ b/linux/keyman-config/keyman_config/list_installed_kmp.py @@ -2,41 +2,14 @@ import os import json -from gi.repository import GObject from keyman_config.kmpmetadata import parsemetadata, parseinfdata -from keyman_config.get_kmp import user_keyman_dir - - -class InstallArea(GObject.GEnum): - IA_OS = 1 - IA_SHARED = 2 - IA_USER = 3 - IA_UNKNOWN = 99 +from keyman_config.get_kmp import get_keyman_dir, InstallLocation +from keyman_config.deprecated_decorator import deprecated +@deprecated('Use get_keyman_dir(area) instead') def get_install_area_path(area): - """ - Get the path of an install area. - - Args: - area (InstallArea): install area to check - InstallArea.IA_USER: ~/.local/share/keyman - InstallArea.IA_SHARED: /usr/local/share/keyman - InstallArea.IA_OS: /usr/share/keyman - InstallArea.IA_UNKNOWN: /usr/share/keyman - - Returns: - string: path of the install area - """ - check_path = "/usr/share/keyman" - if area == InstallArea.IA_USER: - check_path = user_keyman_dir() - elif area == InstallArea.IA_SHARED: - check_path = "/usr/local/share/keyman" - elif area == InstallArea.IA_OS: - check_path = "/usr/share/keyman" - - return check_path + return get_keyman_dir(area) def get_installed_kmp(area): @@ -44,10 +17,10 @@ def get_installed_kmp(area): Get list of installed kmp in an install area. Args: - area (InstallArea): install area to check - InstallArea.IA_USER: ~/.local/share/keyman - InstallArea.IA_SHARED: /usr/local/share/keyman - InstallArea.IA_OS: /usr/share/keyman + area (InstallLocation): install area to check + InstallLocation.User: ~/.local/share/keyman + InstallLocation.Shared: /usr/local/share/keyman + InstallLocation.OS: /usr/share/keyman Returns: list: Installed kmp dict: Keyboard @@ -59,7 +32,7 @@ def get_installed_kmp(area): path (str): base path where keyboard is installed description (str): Keyboard description """ - check_paths = [get_install_area_path(area)] + check_paths = [get_keyman_dir(area)] return get_installed_kmp_paths(check_paths) @@ -143,9 +116,9 @@ def get_kmp_version(packageID): None: if not found """ version = None - user_kmp = get_installed_kmp(InstallArea.IA_USER) - shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) - os_kmp = get_installed_kmp(InstallArea.IA_OS) + user_kmp = get_installed_kmp(InstallLocation.User) + shared_kmp = get_installed_kmp(InstallLocation.Shared) + os_kmp = get_installed_kmp(InstallLocation.OS) if packageID in os_kmp: version = os_kmp[packageID]['version'] @@ -180,7 +153,7 @@ def get_kmp_version_user(packageID): str: kmp version if kmp ID is installed None: if not found """ - user_kmp = get_installed_kmp(InstallArea.IA_USER) + user_kmp = get_installed_kmp(InstallLocation.User) if packageID in user_kmp: return user_kmp[packageID]['version'] else: diff --git a/linux/keyman-config/keyman_config/uninstall_kmp.py b/linux/keyman-config/keyman_config/uninstall_kmp.py index 68d07594e2..0c548c211f 100755 --- a/linux/keyman-config/keyman_config/uninstall_kmp.py +++ b/linux/keyman-config/keyman_config/uninstall_kmp.py @@ -4,7 +4,8 @@ import logging import os.path from shutil import rmtree -from keyman_config.get_kmp import user_keyboard_dir, user_keyman_font_dir +from keyman_config.fcitx_util import is_fcitx_running +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_doc_dir, get_keyman_font_dir from keyman_config.kmpmetadata import get_metadata from keyman_config.ibus_util import uninstall_from_ibus, get_ibus_bus, restart_ibus from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, get_ibus_keyboard_id, is_gnome_shell @@ -17,13 +18,13 @@ def uninstall_kmp_shared(packageID): Args: packageID (str): Keyboard package ID """ - kbdir = os.path.join('/usr/local/share/keyman', packageID) + kbdir = get_keyboard_dir(InstallLocation.Shared, packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) - kbdocdir = os.path.join('/usr/local/share/doc/keyman', packageID) - kbfontdir = os.path.join('/usr/local/share/fonts/keyman', packageID) + kbdocdir = get_keyman_doc_dir(InstallLocation.Shared, packageID) + kbfontdir = get_keyman_font_dir(InstallLocation.Shared, packageID) logging.info("Uninstalling shared keyboard: %s", packageID) if not os.access(kbdir, os.X_OK | os.W_OK): # Check for write access of keyman dir @@ -99,6 +100,10 @@ def uninstall_keyboards_from_gnome(keyboards, packageDir): gnomeKeyboardsUtil.write_input_sources(sources) +def _uninstall_keyboards_from_fcitx5(): + return _('Please use fcitx5-configtool to remove the keyboard from the group') + + def uninstall_kmp_user(packageID): """ Uninstall a kmp from ~/.local/share/keyman @@ -106,14 +111,16 @@ def uninstall_kmp_user(packageID): Args: packageID (str): Keyboard package ID """ - kbdir = user_keyboard_dir(packageID) + kbdir = get_keyboard_dir(InstallLocation.User, packageID) if not os.path.isdir(kbdir): logging.error("Keyboard directory for %s does not exist. Aborting", packageID) exit(3) logging.info("Uninstalling local keyboard: %s", packageID) info, system, options, keyboards, files = get_metadata(kbdir) if keyboards: - if is_gnome_shell(): + if is_fcitx_running(): + _uninstall_keyboards_from_fcitx5() + elif is_gnome_shell(): uninstall_keyboards_from_gnome(keyboards, kbdir) else: uninstall_keyboards_from_ibus(keyboards, kbdir) @@ -121,7 +128,7 @@ def uninstall_kmp_user(packageID): logging.warning("could not uninstall keyboards") rmtree(kbdir) logging.info("Removed user keyman directory: %s", kbdir) - fontdir = os.path.join(user_keyman_font_dir(), packageID) + fontdir = get_keyman_font_dir(InstallLocation.User, packageID) if os.path.isdir(fontdir): rmtree(fontdir) logging.info("Removed user keyman font directory: %s", fontdir) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 6cf9db84f6..d0a2240381 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -7,13 +7,14 @@ import subprocess import sys import gi + gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') from gi.repository import Gtk, GdkPixbuf from keyman_config import _ -from keyman_config.list_installed_kmp import get_install_area_path, get_installed_kmp, InstallArea +from keyman_config.list_installed_kmp import get_installed_kmp from keyman_config.welcome import WelcomeView from keyman_config.options import OptionsView from keyman_config.keyboard_details import KeyboardDetailsView @@ -21,7 +22,7 @@ from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.install_window import InstallKmpWindow, find_keyman_image from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.accelerators import bind_accelerator, init_accel -from keyman_config.get_kmp import user_keyboard_dir +from keyman_config.get_kmp import InstallLocation, get_keyboard_dir, get_keyman_dir class ViewInstalledWindowBase(Gtk.Window): @@ -122,7 +123,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # name str, # version str, # packageID - int, # enum InstallArea (KmpArea is GObject version) + int, # enum InstallLocation (KmpArea is GObject version) str, # path to welcome file if it exists or None str) # path to options file if it exists or None @@ -219,12 +220,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): kmpdata = installed_kmp[kmp] bmppng = ".bmp.png" # Icon file extension - if install_area == InstallArea.IA_USER: - path = user_keyboard_dir(kmpdata['packageID']) - elif install_area == InstallArea.IA_SHARED: - path = os.path.join("/usr/local/share/keyman", kmpdata['packageID']) - else: - path = os.path.join("/usr/share/keyman", kmpdata['packageID']) + path = get_keyboard_dir(install_area, kmpdata['packageID']) welcome_file = os.path.join(path, "welcome.htm") options_file = os.path.join(path, "options.htm") @@ -252,24 +248,24 @@ class ViewInstalledWindow(ViewInstalledWindowBase): logging.debug("Refreshing listview") self.store.clear() self.incomplete_kmp = [] - user_kmp = get_installed_kmp(InstallArea.IA_USER) + user_kmp = get_installed_kmp(InstallLocation.User) for kmp in sorted(user_kmp): kmpdata = user_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(user_kmp, self.store, InstallArea.IA_USER) - shared_kmp = get_installed_kmp(InstallArea.IA_SHARED) + self.addlistitems(user_kmp, self.store, InstallLocation.User) + shared_kmp = get_installed_kmp(InstallLocation.Shared) for kmp in sorted(shared_kmp): kmpdata = shared_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(shared_kmp, self.store, InstallArea.IA_SHARED) - os_kmp = get_installed_kmp(InstallArea.IA_OS) + self.addlistitems(shared_kmp, self.store, InstallLocation.Shared) + os_kmp = get_installed_kmp(InstallLocation.OS) for kmp in sorted(os_kmp): kmpdata = os_kmp[kmp] if kmpdata["has_kbjson"] is False: self.incomplete_kmp.append(kmpdata) - self.addlistitems(os_kmp, self.store, InstallArea.IA_OS) + self.addlistitems(os_kmp, self.store, InstallLocation.OS) def on_tree_selection_changed(self, selection): model, treeiter = selection.get_selected() @@ -284,7 +280,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): _("Settings for keyboard {package}").format(package=model[treeiter][1])) logging.debug("You selected %s version %s", model[treeiter][1], model[treeiter][2]) self.about_button.set_sensitive(True) - if model[treeiter][4] == InstallArea.IA_USER: + if model[treeiter][4] == InstallLocation.User: logging.debug("Enabling uninstall button for %s in %s", model[treeiter][3], model[treeiter][4]) self.uninstall_button.set_sensitive(True) else: @@ -365,7 +361,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None and model[treeiter] is not None: logging.info("Show keyboard details of " + model[treeiter][1]) - areapath = get_install_area_path(model[treeiter][4]) + areapath = get_keyman_dir(model[treeiter][4]) kmp = { "name": model[treeiter][1], "version": model[treeiter][2], "packageID": model[treeiter][3], "areapath": areapath diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index 149c857ae6..4345d25a1c 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -16,7 +16,10 @@ if __name__ == '__main__': parser.add_argument('--version', action='version', version='%(prog)s version ' + versionstring) parser.add_argument('-v', '--verbose', action='store_true', help='verbose logging') parser.add_argument('-vv', '--veryverbose', action='store_true', help='very verbose logging') - parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp package') + parser.add_argument('-i', '--install', action='store', help='download and/or install .kmp ' + + 'package. INSTALL can either be a downloaded .kmp file, a file:// URL ' + + 'pointing to a .kmp file, or a keyman:// URL, possibly with a ' + + 'bcp47= specified.') args = parser.parse_args() if args.verbose: diff --git a/linux/keyman-config/km-config.bash-completion b/linux/keyman-config/km-config.bash-completion new file mode 100644 index 0000000000..bc28b19212 --- /dev/null +++ b/linux/keyman-config/km-config.bash-completion @@ -0,0 +1,36 @@ +#/usr/bin/env bash + +_km-config_completions() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-h --help -v --verbose -vv --veryverbose --version -i --install" + + if [[ ${cur} == -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + # default IFS splits 'file://' into 'file', ':' and '//' + if [ "${prev}" == ":" -a "${COMP_WORDS[COMP_CWORD-2]}" == "file" ]; then + local flag=${COMP_WORDS[COMP_CWORD-3]} + if [ $flag == "-i" -o $flag == "--install" ]; then + prev=$flag + fi + fi + + case "${prev}" in + "-i"|"--install") + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) ) + return 0 + ;; + *) + ;; + esac +} + +complete -F _km-config_completions km-config diff --git a/linux/keyman-config/km-kvk2ldml.bash-completion b/linux/keyman-config/km-kvk2ldml.bash-completion index ab40c3ef21..c89967666f 100644 --- a/linux/keyman-config/km-kvk2ldml.bash-completion +++ b/linux/keyman-config/km-kvk2ldml.bash-completion @@ -12,6 +12,21 @@ _km-kvk2ldml_completions() COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi + + case "${prev}" in + "-o"|"--output") + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -- $cur) ) + return 0 + ;; + *) + ;; + esac + + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -X "!"*.kvk -- $cur) $(compgen -d -- $cur) ) } complete -F _km-kvk2ldml_completions km-kvk2ldml diff --git a/linux/keyman-config/km-package-get.bash-completion b/linux/keyman-config/km-package-get.bash-completion index 48b96b942f..1e8f66dab3 100644 --- a/linux/keyman-config/km-package-get.bash-completion +++ b/linux/keyman-config/km-package-get.bash-completion @@ -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 diff --git a/linux/keyman-config/km-package-install.bash-completion b/linux/keyman-config/km-package-install.bash-completion index 8833ed411e..4525c930fa 100644 --- a/linux/keyman-config/km-package-install.bash-completion +++ b/linux/keyman-config/km-package-install.bash-completion @@ -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 diff --git a/linux/keyman-config/km-package-list-installed b/linux/keyman-config/km-package-list-installed index fd7baa5e32..4f991ef22e 100755 --- a/linux/keyman-config/km-package-list-installed +++ b/linux/keyman-config/km-package-list-installed @@ -25,26 +25,27 @@ def main(): logging.basicConfig(format='%(levelname)s:%(message)s') all = not args.user and not args.shared and not args.os - from keyman_config.list_installed_kmp import get_installed_kmp, get_install_area_path, InstallArea + from keyman_config.list_installed_kmp import get_installed_kmp + from keyman_config.get_kmp import get_keyman_dir, InstallLocation if args.user or all: - installed_kmp = get_installed_kmp(InstallArea.IA_USER) + installed_kmp = get_installed_kmp(InstallLocation.User) if args.verbose or args.veryverbose: - print("--- Installed user Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_USER))) + print("--- Installed user Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.User))) else: print("--- Installed user Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) if args.shared or all: - installed_kmp = get_installed_kmp(InstallArea.IA_SHARED) + installed_kmp = get_installed_kmp(InstallLocation.Shared) if args.verbose or args.veryverbose: print("--- Installed shared Keyman keyboard packages (%s) ---" % - (get_install_area_path(InstallArea.IA_SHARED))) + (get_keyman_dir(InstallLocation.Shared))) else: print("--- Installed shared Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) if args.os or all: - installed_kmp = get_installed_kmp(InstallArea.IA_OS) + installed_kmp = get_installed_kmp(InstallLocation.OS) if args.verbose or args.veryverbose: - print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_install_area_path(InstallArea.IA_OS))) + print("--- Installed OS Keyman keyboard packages (%s) ---" % (get_keyman_dir(InstallLocation.OS))) else: print("--- Installed OS Keyman keyboard packages ---") print_keyboards(installed_kmp, args.long) diff --git a/linux/keyman-config/km-package-uninstall.bash-completion b/linux/keyman-config/km-package-uninstall.bash-completion index ff07a31580..451a2e3e80 100644 --- a/linux/keyman-config/km-package-uninstall.bash-completion +++ b/linux/keyman-config/km-package-uninstall.bash-completion @@ -23,11 +23,11 @@ _km-package-uninstall_completions() shared="" case "${prev}" in "-s"|"--shared") - for file in `ls -d /usr/local/share/keyman/*/`; do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done + for file in $(ls -1 /usr/local/share/keyman); do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done COMPREPLY=($(compgen -W "${shared}" -- ${cur})) ;; *) - for file in `ls -d ~/.local/share/keyman/*/`; do kbid="`basename ${file}`"; words="${words} ${kbid}"; done + for file in $(ls -1 ${XDG_DATA_HOME:-~/.local/share}/keyman); do kbid="`basename ${file}`"; words="${words} ${kbid}"; done COMPREPLY=($(compgen -W "${words}" -- ${cur})) ;; esac diff --git a/linux/scripts/cow.sh b/linux/scripts/cow.sh index bee245c8fb..d89cf14d0a 100755 --- a/linux/scripts/cow.sh +++ b/linux/scripts/cow.sh @@ -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 diff --git a/linux/scripts/launchpad.sh b/linux/scripts/launchpad.sh index 4bfe7d6e3e..54f745a11c 100755 --- a/linux/scripts/launchpad.sh +++ b/linux/scripts/launchpad.sh @@ -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 diff --git a/mac/README.md b/mac/README.md index 7e614cde69..32c59cafe1 100644 --- a/mac/README.md +++ b/mac/README.md @@ -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 diff --git a/resources/devbox/macos/keyman.macos.env.sh b/resources/devbox/macos/keyman.macos.env.sh new file mode 100644 index 0000000000..b6fc5b83d8 --- /dev/null +++ b/resources/devbox/macos/keyman.macos.env.sh @@ -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 diff --git a/resources/devbox/macos/macos.sh b/resources/devbox/macos/macos.sh new file mode 100755 index 0000000000..a5e7232e46 --- /dev/null +++ b/resources/devbox/macos/macos.sh @@ -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 diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index b75a218b20..d54cc292f0 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index f98534fb44..d5c8d68fc2 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.74", + "version": "15.0.79", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 2004c12035..6d763ccf58 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index 2a771ffb97..a66717cce1 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.74", + "version": "15.0.79", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.74" + "@keymanapp/resources-gosh": "^15.0.79" } } diff --git a/web/README.md b/web/README.md index 01d6a5da41..c46288d05c 100644 --- a/web/README.md +++ b/web/README.md @@ -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. ********************************************************************** diff --git a/web/package-lock.json b/web/package-lock.json index dac9e4b03a..ad6eeaa906 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.74", + "version": "15.0.79", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index 04a88af3ab..d21c44d729 100644 --- a/web/package.json +++ b/web/package.json @@ -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" diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 01d22c3e9c..eacc228c74 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1474,10 +1474,10 @@ namespace com.keyman.dom { /** * Function Initialization * Scope Public - * @param {Object} arg object array of user-defined properties + * @param {com.keyman.OptionType} arg object of user-defined properties * Description KMW window initialization */ - init: (arg:any) => Promise = function(this: DOMManager, arg): Promise { + init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { var i,j,c,e,p,eTextArea,eInput,opt,dTrailer,ds; var osk = this.keyman.osk; var util = this.keyman.util; diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 5abd9f08a6..8b681b6763 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -1,4 +1,6 @@ /// +// Includes a promise polyfill (needed for IE) +/// namespace com.keyman.keyboards { class CloudRequestEntry { @@ -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} Promise of added keyboard stubs **/ - keymanCloudRequest(cmd: string, byLanguage?: boolean) { + keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise { 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(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 { } } } -} \ No newline at end of file +} diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index 461a102ed1..64669a4498 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -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(); })(); -} \ No newline at end of file +} diff --git a/web/source/kmwembedded.ts b/web/source/kmwembedded.ts index 393f32727d..1c8fbbf692 100644 --- a/web/source/kmwembedded.ts +++ b/web/source/kmwembedded.ts @@ -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; diff --git a/web/source/kmwnative.ts b/web/source/kmwnative.ts index 4231922bfa..6bf7c3e949 100644 --- a/web/source/kmwnative.ts +++ b/web/source/kmwnative.ts @@ -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) { diff --git a/web/source/kmwutils.ts b/web/source/kmwutils.ts index 581cb05731..d19b1c475a 100644 --- a/web/source/kmwutils.ts +++ b/web/source/kmwutils.ts @@ -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; \ No newline at end of file +import Util = com.keyman.Util; diff --git a/web/source/osk/oskManager.ts b/web/source/osk/oskManager.ts index 4eb5d5dcfb..c9add000a4 100644 --- a/web/source/osk/oskManager.ts +++ b/web/source/osk/oskManager.ts @@ -313,7 +313,11 @@ namespace com.keyman.osk { source: text.MutableSystemStore, newValue: string) { if(source.value != newValue) { - this.vkbd.layerId = newValue; + // Prevents console errors when a keyboard only displays help. + // Can occur when using SHIFT with sil_euro_latin on a desktop form-factor. + if(this.vkbd) { + this.vkbd.layerId = newValue; + } this._Show(); } }.bind(this); @@ -479,8 +483,8 @@ namespace com.keyman.osk { */ showBuild() { let keymanweb = com.keyman.singleton; - keymanweb.util.alert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'

' - +'Copyright © 2017 SIL International'); + keymanweb.util.internalAlert('KeymanWeb Version '+keymanweb['version']+'.'+keymanweb['build']+'

' + +'Copyright © 2017 SIL International'); } /** diff --git a/web/testing/attachment-api/index.html b/web/testing/attachment-api/index.html index d4bfe758d2..37cb68476c 100644 --- a/web/testing/attachment-api/index.html +++ b/web/testing/attachment-api/index.html @@ -2,79 +2,68 @@ - - - + + + - + - - + + KeymanWeb Sample Page - Uncompiled Source - - - - + #KeymanWebControl {width:50%;min-width:600px;} + + + - - + - + // 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 : '' + }); + + - - + + - +

KeymanWeb Sample Page - Attachment API Testing

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

-

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

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

Be sure to reference the developer console for additional feedback.

-

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

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

+ + + + + + + + + + + + + + +

KeymanWeb Sample Page - Promise API

+

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

+

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

+

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

+ +

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

+

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

+ + + +
+ +
+ +

Type in your language in this text area:

+ + +

or in this input field:

+ + + +

Add a keyboard by keyboard name:

+ + + +

Add a keyboard by BCP-47 language code:

+ + + +

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

+ + + +

Return to testing home page

+
+ + + + + diff --git a/web/testing/promise-api/promisehdr.js b/web/testing/promise-api/promisehdr.js new file mode 100644 index 0000000000..3dc41a4b2b --- /dev/null +++ b/web/testing/promise-api/promisehdr.js @@ -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); + } + diff --git a/web/testing/unminified.html b/web/testing/unminified.html index 0e850a8ce9..4f1a28c731 100644 --- a/web/testing/unminified.html +++ b/web/testing/unminified.html @@ -2,38 +2,38 @@ - - - + + + - + - - + + KeymanWeb Sample Page - Unminified Source - - - - + #KeymanWebControl {width:50%;min-width:600px;} + + + - - + - + + - + - - + +

KeymanWeb Sample Page - Unminified Source

- -
+

Type in your language in this text area:

- +

or in this input field:

- +

Add a keyboard by keyboard name:

- +

Add a keyboard by BCP-47 language code:

-

Add a keyboard by language name:

+

Add a keyboard by language name(s):

- +

Return to testing home page

- - + * + *** + --> diff --git a/windows/src/developer/TIKE/Tike.dpr b/windows/src/developer/TIKE/Tike.dpr index 3977f72f49..316e85f283 100644 --- a/windows/src/developer/TIKE/Tike.dpr +++ b/windows/src/developer/TIKE/Tike.dpr @@ -317,7 +317,6 @@ begin Application.Title := 'Keyman Developer'; //TBX.TBXSetTheme('OfficeXP2'); if TikeActive then Exit; - InitClasses; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); Application.Run; diff --git a/windows/src/developer/TIKE/main/UfrmMain.pas b/windows/src/developer/TIKE/main/UfrmMain.pas index 5185a4b927..30e4d9e9ff 100644 --- a/windows/src/developer/TIKE/main/UfrmMain.pas +++ b/windows/src/developer/TIKE/main/UfrmMain.pas @@ -1,18 +1,18 @@ (* Name: UfrmMain Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 20 Jun 2006 Modified Date: 24 Aug 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 20 Jun 2006 - mcdurdin - Initial version 01 Aug 2006 - mcdurdin - Rework ui using sp-TBX 02 Aug 2006 - mcdurdin - Remove old menu @@ -410,15 +410,12 @@ type var frmKeymanDeveloper: TfrmKeymanDeveloper; -procedure InitClasses; - implementation uses System.Math, Winapi.WinInet, Winapi.ShlObj, - Winapi.Urlmon, Winapi.UxTheme, System.Win.ComObj, Vcl.Themes, @@ -1544,12 +1541,5 @@ begin ProjectForm.SetGlobalProject; end; -procedure InitClasses; // I3350 -const - CUserAgent: AnsiString = 'Mozilla/5.0 (compatible; MSIE 11.0; Windows NT 6.1; WOW64; Trident/5.0; TIKE/'+SKeymanVersion+')'; // I4045 -begin - OleCheck(UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, PAnsiChar(CUserAgent), Length(CUserAgent), 0)); // I4045 -end; - end. diff --git a/windows/src/developer/TIKE/xml/app/editor/editor.js b/windows/src/developer/TIKE/xml/app/editor/editor.js index ce070f0115..be447ac965 100644 --- a/windows/src/developer/TIKE/xml/app/editor/editor.js +++ b/windows/src/developer/TIKE/xml/app/editor/editor.js @@ -389,7 +389,7 @@ async function loadSettings() { var commands = []; var command = function (cmd) { - if (navigator.userAgent.indexOf('TIKE') < 0) { + if (navigator.userAgent.indexOf('Keyman') < 0) { // Don't execute command if not in the correct host application (allows for browser-based testing) return false; } diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js index 1e9133c2b0..4becea28f7 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js @@ -1119,7 +1119,7 @@ $(function () { this.commands = []; this.command = function (cmd) { - if (navigator.userAgent.indexOf('TIKE') < 0) { + if (navigator.userAgent.indexOf('Keyman') < 0) { return false; } // queue commands to build a single portmanteau command when we return to idle diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index 6bf4a4deb3..1ca6eff025 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -68,7 +68,7 @@ WCHAR *AppContext::Buf(int n) return p; } -WCHAR *AppContext::BufMax(int n) // Used only by IMX DLLs +WCHAR *AppContext::BufMax(int n) { WCHAR *p = wcschr(CurContext, 0); // I3091 @@ -107,14 +107,22 @@ void AppContext::Reset() void AppContext::Get(WCHAR *buf, int bufsize) { - for(WCHAR *p = CurContext; *p && bufsize > 0; p++, bufsize--) - { - *buf = *p; buf++; - if(*p >= 0xD800 && *p <= 0xDBFF) { *buf = *(++p); bufsize--; buf++; } - } - //wcsncpy(buf, CurContext, bufsize); - *buf = 0; - //buf[bufsize-1] = 0; + // surrogate pairs need to be treated as a single unit, therefore use + // BufMax to find a start index. + // BufMax handles the case where a surrogate pair at the + // start of the buffer is split by bufsize + for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) + { + *buf = *p; + if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) { + buf++; p++; + *buf = *p; + bufsize--; + } + buf++; + } + + *buf = 0; } void AppContext::CopyFrom(AppContext *source) // I3575 diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 748eca5522..55f6c4a693 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -1,18 +1,18 @@ /* Name: appint Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 14 Sep 2006 Modified Date: 23 Feb 2016 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 14 Sep 2006 - mcdurdin - Remove NoSetShift flag 27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version @@ -74,7 +74,7 @@ public: AppActionQueue(); virtual void ResetQueue(); virtual BOOL QueueAction(int ItemType, DWORD dwData); - + BOOL IsQueueEmpty() { return QueueSize == 0; } int GetQueueSize() { return QueueSize; } }; @@ -82,20 +82,88 @@ public: class AppContext { private: - WCHAR CurContext[MAXCONTEXT]; + WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated. int pos; public: AppContext(); + /** + * @brief Copy "source" AppContext to this AppContext + * + * @param source AppContext to copy + */ void CopyFrom(AppContext *source); - void Add(WCHAR ch); + + /** + * @brief Add a single code unit to the Current Context. Not necessarily a complete code point + * + * @param Code unit to add + */ + void Add(WCHAR ch); + + /** + * @brief Removes a single code point from the end of the CurContext closest to the caret; + * 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(); - void Reset(); + + /** + * @brief Clears the CurContext and resets the position - pos - index + */ + void Reset(); + + /** + * @brief Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize The number of code units ie size of the WCHAR buffer - not the code points + */ void Get(WCHAR *buf, int bufsize); + + /** + * @brief Sets the CurContext to the supplied buf character array and updates the pos index. + * + * @param buf + */ void Set(const WCHAR *buf); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n valid xstring units remaining until the null terminating + * character. It will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair or deadkey. + * + * @param n The maximum number of valid xstring units (not code points or code units) + * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units + */ WCHAR *BufMax(int n); + + /** + * @brief Returns a pointer to the character in the current context buffer which + * will have at most n code units remaining until the the null terminating character. + * Note: Unlike BufMax there is no checking for truncation of code points. + * e.g. the pointer may point to a code unit that is half of a surrogate pair. + * + * @param n + * @return WCHAR* Pointer to the start postion for a buffer of maximum n characters + */ WCHAR *Buf(int n); + + /** + * @brief Returns TRUE if the last xstring unit in the context is a deadkey + * + * @return BOOL + */ BOOL CharIsDeadkey(); + + /** + * @brief Returns TRUE if the last xstring unit in the CurContext is a surrogate pair. + * @return BOOL + */ BOOL CharIsSurrogatePair(); }; @@ -119,28 +187,27 @@ public: //BOOL NoSetShift; /* Information functions */ - + virtual BOOL CanHandleWindow(HWND ahwnd) = 0; virtual BOOL IsWindowHandled(HWND ahwnd) = 0; virtual BOOL HandleWindow(HWND ahwnd) = 0; virtual BOOL IsUnicode() = 0; /* Context functions */ - + virtual void ReadContext() = 0; virtual void ResetContext() = 0; virtual void AddContext(WCHAR ch) = 0; //I2436 virtual WCHAR *ContextBuf(int n) = 0; virtual WCHAR *ContextBufMax(int n) = 0; - + /* Queue and sending functions */ - + virtual BOOL QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags) = 0; void SetCurrentShiftState(int ShiftFlags) { FShiftFlags = ShiftFlags; } virtual BOOL SendActions() = 0; // I4196 }; extern const LPSTR ItemTypes[]; - -#endif +#endif diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp b/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp new file mode 100644 index 0000000000..4b784ca14f --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp @@ -0,0 +1,41 @@ +#include "pch.h" + + +// Test calling buffer of MAXCONTEXT size +TEST(AppContext, Get_Max_buff) { + WCHAR callbuf[MAXCONTEXT]; + AppContext testContext; + WCHAR *testString = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789"; + WCHAR *expectedString = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0"; // MAXCONTEXT taking into account null character termination + testContext.Set(testString); + testContext.Get(callbuf, MAXCONTEXT); + EXPECT_STREQ((expectedString), callbuf); +} +// Test for a calling buffer smaller than AppContext's current context size. +// Expected result should return subset with charcters closet +// to the caret. +TEST(AppContext, Get_small_buff) { + WCHAR callbuf[MAXCONTEXT]; + AppContext testContext; + testContext.Set(L"abcdefghijk"); + testContext.Get(callbuf, 5); + EXPECT_STREQ((L"ghijk"), callbuf); +} + +// Test with the calling buffer size that would only have enough bytes for one +// surrogate of a surrogate pair. +TEST(AppContext, Get_split_surrogate) { + + WCHAR callbuff[MAXCONTEXT]; + AppContext testContext; + WCHAR testString[] = { 0x0042, 0x0042, 0xD800, 0xDC00, 0x0043, 0x0044, 0x0000 }; + WCHAR expectedString[] = { 0x0043, 0x0044, 0x0000 }; + + testContext.Set(testString); + // split the surrogate should return on 16-bit code unit less than requested + testContext.Get(callbuff, 3); + EXPECT_STREQ((expectedString), callbuff); + // This will should be the same size as the split surrogate + testContext.Get(callbuff, 2); + EXPECT_STREQ((expectedString), callbuff); +} diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj index ad0235d603..abcd41e766 100644 --- a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj @@ -54,7 +54,7 @@ - + Create Create @@ -69,6 +69,9 @@ {a7069186-5ae7-4504-8926-861b7a34ba9e} + + {bd5564fb-35a5-4a3c-b96a-4a6578e2b593} + @@ -88,6 +91,7 @@ true Console + version.lib;psapi.lib;imm32.lib;%(AdditionalDependencies) diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp b/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp deleted file mode 100644 index a72be13e58..0000000000 --- a/windows/src/engine/keyman32/tests/keyman32-tests/test.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "pch.h" - - -TEST(TestCaseName, TestName) { - EXPECT_EQ(1, 1); - EXPECT_TRUE(true); -} - -//int xstrlen(PWSTR p); - -TEST(xstring, xstrlen) { - EXPECT_EQ(3, xstrlen(L"abc")); -} diff --git a/windows/src/global/delphi/comp/OnScreenKeyboard.pas b/windows/src/global/delphi/comp/OnScreenKeyboard.pas index f045d39e39..1a48b0d051 100644 --- a/windows/src/global/delphi/comp/OnScreenKeyboard.pas +++ b/windows/src/global/delphi/comp/OnScreenKeyboard.pas @@ -1,18 +1,18 @@ (* Name: OnScreenKeyboard Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 23 Aug 2006 Modified Date: 24 Jul 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 23 Aug 2006 - mcdurdin - Initial version 30 Aug 2006 - mcdurdin - Fix underlying layout published bug 14 Sep 2006 - mcdurdin - Add Transparent flag, retrieve aspected size, polish drawing @@ -264,6 +264,8 @@ const // I4098 // I4799 implementation uses + System.Math, + Glossary, ScanCodeMap, Types, @@ -379,7 +381,7 @@ begin ParentBackground := True; UpdateEuroLayout; // I764 - + if HasParent then // I1227, I904, I1102 - crash export OSK to BMP, PNG CalcKeyFontSizes; end; @@ -730,7 +732,7 @@ begin begin k2 := FHoverKey; if Assigned(k) and not k.Enabled then k := nil; - + FHoverKey := k; DoDrawKey(Canvas, k); //InvalidateKey(k); DoDrawKey(Canvas, k2); //InvalidateKey(k2); @@ -1383,16 +1385,27 @@ var if GetTextExtentPoint32W(Canvas.Handle, PWideChar(FKeyValue), Length(FKeyValue), FTextExtent) then // I2576 begin - if FTextExtent.cx > (RText.Right - RText.Left) * FKeyboard.FScale then + if (FTextExtent.cx > (RText.Right - RText.Left) * FKeyboard.FScale) or + (FTextExtent.cy > (RText.Bottom - RText.Top) * FKeyboard.FScale) then begin - Font.Height := Trunc(Font.Height * (RText.Right - RText.Left) * FKeyboard.FScale / FTextExtent.cx); - end - else if FTextExtent.cy > (RText.Bottom - RText.Top) * FKeyboard.FScale then - begin - Font.Height := Trunc(Font.Height * (RText.Bottom - RText.Top) * FKeyboard.FScale / FTextExtent.cy); - end + // font.height may be either positive or negative, depending on whether + // internal leading is included. It doesn't matter, so long as we keep + // the same factor. + if Font.Height < 0 then + Font.Height := System.Math.Max( + Trunc(Font.Height * (RText.Right - RText.Left) * FKeyboard.FScale / FTextExtent.cx), + Trunc(Font.Height * (RText.Bottom - RText.Top) * FKeyboard.FScale / FTextExtent.cy) + ) + else + Font.Height := System.Math.Min( + Trunc(Font.Height * (RText.Right - RText.Left) * FKeyboard.FScale / FTextExtent.cx), + Trunc(Font.Height * (RText.Bottom - RText.Top) * FKeyboard.FScale / FTextExtent.cy) + ); + end; end; + // TODO: in future re-scale the font here per designer's preference + // but this requires a file format change, so a much bigger scope if not Enabled then Font.Color := $808080 else Font.Color := clWindowText; diff --git a/windows/src/global/inst/data/keyboard_info/README.md b/windows/src/global/inst/data/keyboard_info/README.md index fd5149ef16..7b45ebe72b 100644 --- a/windows/src/global/inst/data/keyboard_info/README.md +++ b/windows/src/global/inst/data/keyboard_info/README.md @@ -9,6 +9,12 @@ New versions should be deployed to **keymanapp/keyman/windows/src/global/inst/ke # .keyboard_info version history +## 2019-09-06 1.0.6 stable +* No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). + +## 2018-12-18 1.0.5 stable +* Deprecate languages[] array. Update KeyboardLanguageInfo object to include subtag names. + ## 2018-11-26 1.0.4 stable * Add helpLink field - a link to a keyboard's help page on help.keyman.com if it exists. diff --git a/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json b/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json index 96d2207bb5..ac2687701f 100644 --- a/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json +++ b/windows/src/global/inst/data/keyboard_info/keyboard_info.source.json @@ -1,7 +1,7 @@ -{ +{ "$schema": "http://json-schema.org/schema#", "$ref": "#/definitions/KeyboardInfo", - + "definitions": { "KeyboardInfo": { "type": "object", @@ -28,23 +28,23 @@ "encodings": { "type": "array", "items": { "type": "string", "enum": ["ansi", "unicode"] }, "additionalItems": false }, "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["welcome", "documentation", "fonts", "visualKeyboard"] }, "additionalItems": false }, "version": { "type": "string" }, - "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.0$" }, + "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.\\d$" }, "helpLink": { "type": "string", "pattern": "^https://help\\.keyman\\.com/keyboard/" }, "platformSupport": { "$ref": "#/definitions/KeyboardPlatformInfo" }, "legacyId": { "type": "number" }, "sourcePath": { "type": "string", "pattern": "^(release|legacy|experimental)/.+/.+$" }, - "related": { "type": "object", "patternProperties": { + "related": { "type": "object", "patternProperties": { ".": { "$ref": "#/definitions/KeyboardRelatedInfo" } }, "additionalProperties": false - } + }, + "deprecated": { "type": "boolean" } }, "required": [ "license", "languages" - ], - "additionalProperties": false + ] }, - + "KeyboardLanguageInfo": { "type": "object", "patternProperties": { @@ -52,18 +52,22 @@ }, "additionalProperties": false }, - + "KeyboardLanguageInfoItem": { "type": "object", "properties": { "font": { "$ref": "#/definitions/KeyboardFontInfo" }, "oskFont": { "$ref": "#/definitions/KeyboardFontInfo" }, - "example": { "$ref": "#/definitions/KeyboardExampleInfo" } + "example": { "$ref": "#/definitions/KeyboardExampleInfo" }, + "displayName": { "type": "string" }, + "languageName": { "type": "string" }, + "scriptName": { "type": "string" }, + "regionName": { "type": "string" } }, "required": [], "additionalProperties": false }, - + "KeyboardFontInfo": { "type": "object", "properties": { @@ -81,9 +85,9 @@ "KeyboardExampleInfo": { "type": "object", "properties": { - "keys": { "anyOf": [ + "keys": { "anyOf": [ { "type": "string" }, - { "type": "array", "items": { + { "type": "array", "items": { "anyOf": [ { "type": "string" }, { "$ref": "#/definitions/KeyboardExampleKeyInfo" } @@ -96,7 +100,7 @@ "required": [], "additionalProperties": false }, - + "KeyboardExampleKeyInfo": { "type": "object", "properties": { @@ -106,7 +110,7 @@ "K_N", "K_O", "K_P", "K_Q", "K_R", "K_S", "K_T", "K_U", "K_V", "K_W", "K_X", "K_Y", "K_Z", "K_1", "K_2", "K_3", "K_4", "K_5", "K_6", "K_7", "K_8", "K_9", "K_0", "K_BKQUOTE", "K_HYPHEN", "K_EQUAL", "K_LBRKT", "K_RBRKT", "K_BKSLASH", "K_COLON", - "K_QUOTE", "K_COMMA", "K_PERIOD", "K_SLASH", + "K_QUOTE", "K_COMMA", "K_PERIOD", "K_SLASH", "K_oE2", "K_BKSP", "K_TAB", "K_ENTER", "K_ESC", "K_LEFT", "K_UP", "K_RIGHT", "K_DOWN", "K_PGUP", "K_PGDN", "K_HOME", "K_END", "K_INS", "K_DEL", "K_F1", "K_F2", "K_F3", "K_F4", "K_F5", "K_F6", "K_F7", "K_F8", "K_F9", "K_F10", "K_F11", "K_F12", @@ -135,7 +139,7 @@ "required": ["key"], "additionalProperties": false }, - + "KeyboardLinkInfo": { "type": "object", "properties": { @@ -145,7 +149,7 @@ "required": ["name", "url"], "additionalProperties": false }, - + "KeyboardPlatformInfo": { "type": "object", "patternProperties": { @@ -154,7 +158,7 @@ "required": [], "additionalProperties": false }, - + "KeyboardRelatedInfo": { "type": "object", "properties": {