mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 08:37:42 +00:00
Merge pull request #4624 from keymanapp/chore/beta-to-alpha-B14S7
chore: beta to alpha merge, B14S7
This commit is contained in:
commit
fedbb1e025
215 changed files with 4186 additions and 1364 deletions
82
HISTORY.md
82
HISTORY.md
|
|
@ -232,6 +232,88 @@
|
|||
|
||||
* chore: prepare 15.0 alpha (#4129)
|
||||
|
||||
## 14.0.253 beta 2021-03-05
|
||||
|
||||
* fix(android/app): Fix welcome.htm responsiveness (#4531)
|
||||
* feat(developer): &CasedKeys system store (#4586)
|
||||
* fix(android): Localize Toast notifications (#4588)
|
||||
* fix(windows): PreservedKeyMap::MapUSCharToVK line order bug (#4595)
|
||||
* fix(developer): tidy up expansions tests (#4592)
|
||||
* fix(ios): adds i18n for some error alerts (#4577)
|
||||
* fix(windows): incxstr could run over buffer with malformed data (#4596)
|
||||
* chore(android/app): Update whatsnew with available display languages (#4610)
|
||||
* chore(linux): Improve Sentry environment setting (#4589)
|
||||
* fix(developer): Expand filenames before load (#4606)
|
||||
|
||||
## 14.0.252 beta 2021-03-04
|
||||
|
||||
* feat(ios): enables de, fr, and km localizations (#4585)
|
||||
* fix(developer): improve CEF location search stability (#4571)
|
||||
* fix(developer): Support all fonts in Keyboard Fonts dialog (#4574)
|
||||
* feat(developer): Add different Open Containing Folder buttons (#4576)
|
||||
* feat(developer): Range expansions (#4584)
|
||||
* fix(web): fixes lack of respect for underlying-key display settings (#4572)
|
||||
* fix(common/models): fixes application of suggestions immediately after a backspace (#4587)
|
||||
* fix(linux): Improve version number (#4582)
|
||||
* chore(linux): Don't report to Sentry in dev environment (#4581)
|
||||
|
||||
## 14.0.251 beta 2021-03-03
|
||||
|
||||
* fix(developer): Improve stability of named code constants (#4547)
|
||||
* fix(developer): schema conformance for model package compiler (#4548)
|
||||
* fix(developer): touch layout osk import handling of multiple modifiers (#4552)
|
||||
* fix(developer): require language tag when compiling keyboard package (#4563)
|
||||
* fix(developer): Avoid blank keys when importing KMX to KVKS (#4564)
|
||||
* feat(developer): isRTL support for lexical model editor (#4559)
|
||||
* fix(developer): track modified state in wordlist editor better (#4562)
|
||||
* chore(ios): better visual feedback for keyboard search during poor internet connectivity (#4573)
|
||||
* chore(common): Update crowdin files for `de` (#4578)
|
||||
* chore(common/core/desktop): write debug output to console (#4569)
|
||||
|
||||
## 14.0.250 beta 2021-03-02
|
||||
|
||||
* fix(common/resources): Fix help.keyman.com path for CI (#4565)
|
||||
|
||||
## 14.0.249 beta 2021-03-01
|
||||
|
||||
* fix(web): mnemonic keystrokes w FF keymapping (#4540)
|
||||
* chore(ios/app): Adjust help titles for installing custom dictionaries (#4550)
|
||||
|
||||
## 14.0.248 beta 2021-02-26
|
||||
|
||||
* fix(common/models): predictions after context reset / caret shift (#4411)
|
||||
* change(oem/fv/ios): FV keyboards now package-based (#4471)
|
||||
* fix(windows): Handle Caps Lock event correctly from TIP (#4536)
|
||||
* fix(developer): run even if sentry unavailable (#4537)
|
||||
* fix(developer): UTF-8 messages in LM compiler (#4539)
|
||||
* feat(common/models): mid-context suggestions & reversions, fix(common/models): correction-search SMP issues (#4427)
|
||||
* fix(ios): package installer completion requires welcome dismissal (#4543)
|
||||
* fix(windows): Refresh settings on 64-bit apps (#4378)
|
||||
* fix(windows): prevent re-registration of TIPs on 14.0 upgrade (#4535)
|
||||
|
||||
## 14.0.247 beta 2021-02-25
|
||||
|
||||
* fix(web): keyboard documentation patch-up (#4512)
|
||||
* fix(web): removes package namespacing from kbd's CSS class (#4516)
|
||||
|
||||
## 14.0.246 beta 2021-02-24
|
||||
|
||||
* chore(common): allow forced version increment (#4522)
|
||||
|
||||
## 14.0.245 beta 2021-02-24
|
||||
|
||||
* fix(common/core/web): core key-processing now always returns RuleBehavior type. (#4508)
|
||||
* fix(common/resources): Set help-keyman.com.sh executable (#4510)
|
||||
* fix(developer/compilers): fixes error when "constructor" is in wordlist (#4504)
|
||||
* fix(web): hides touch-alias caret when keystroke causes focus change (#4514)
|
||||
|
||||
## 14.0.244 beta 2021-02-22
|
||||
|
||||
* fix(common/models): merges identical suggestions after casing (#4502)
|
||||
* fix(web): macOS 11 agent string parsing (#4497)
|
||||
* fix(ios): app logging messages were transient, never stored (#4500)
|
||||
* chore(ios): web-side sentry enablement try-catch (#4492)
|
||||
|
||||
## 14.0.243 beta 2021-02-12
|
||||
|
||||
* change(ios/app): Generate offline help from markdown (#4470)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.0.19
|
||||
15.0.19
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.os.Handler;
|
|||
import android.os.ResultReceiver;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
|
@ -27,8 +28,7 @@ public class DownloadResultReceiver extends ResultReceiver {
|
|||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
switch(resultCode) {
|
||||
case FileUtils.DOWNLOAD_ERROR :
|
||||
Toast.makeText(context, "Download failed",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.download_failed, Toast.LENGTH_SHORT);
|
||||
MainActivity.cleanupPackageInstall();
|
||||
break;
|
||||
case FileUtils.DOWNLOAD_SUCCESS :
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (C) 2020 SIL International. All rights reserved.
|
||||
* Copyright (C) 2020-2021 SIL International. All rights reserved.
|
||||
*/
|
||||
|
||||
package com.tavultesoft.kmapro;
|
||||
|
|
@ -13,6 +13,7 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
|
|
@ -22,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
import com.tavultesoft.kmea.util.KMPLink;
|
||||
import com.tavultesoft.kmea.util.WebViewUtil;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -162,6 +164,7 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
WebViewUtil.cleanup(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
/**
|
||||
* Copyright (C) SIL International. All rights reserved.
|
||||
*/
|
||||
package com.tavultesoft.kmapro;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
|
@ -9,17 +11,8 @@ import androidx.fragment.app.Fragment;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
|
|
@ -36,9 +29,7 @@ import com.tavultesoft.kmea.util.KMLog;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -254,9 +245,6 @@ public class PackageActivity extends AppCompatActivity implements
|
|||
languageList = new ArrayList<String>();
|
||||
}
|
||||
|
||||
//Dataset kmpDataset = new Dataset(context);
|
||||
//kmpDataset.keyboards.addAll(kbdsList);
|
||||
|
||||
List<Map<String, String>> installedPackageKeyboards =
|
||||
kmpProcessor.processKMP(kmpFile, tempPackagePath, PackageProcessor.PP_KEYBOARDS_KEY, languageList);
|
||||
// Do the notifications!
|
||||
|
|
@ -270,8 +258,6 @@ public class PackageActivity extends AppCompatActivity implements
|
|||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
_cleanup = true;
|
||||
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED,
|
||||
installedPackageKeyboards, 1);
|
||||
if (installedPackageKeyboards != null) {
|
||||
notifyPackageInstallListeners(KeyboardEventHandler.EventType.PACKAGE_INSTALLED,
|
||||
installedPackageKeyboards, 1);
|
||||
|
|
@ -294,8 +280,6 @@ public class PackageActivity extends AppCompatActivity implements
|
|||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
_cleanup = true;
|
||||
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
|
||||
installedLexicalModels, 1);
|
||||
if (installedLexicalModels != null) {
|
||||
notifyLexicalModelInstallListeners(KeyboardEventHandler.EventType.LEXICAL_MODEL_INSTALLED,
|
||||
installedLexicalModels, 1);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.stepstone.stepper.StepperLayout;
|
|||
import com.stepstone.stepper.VerificationError;
|
||||
import com.tavultesoft.kmea.packages.PackageProcessor;
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
import com.tavultesoft.kmea.util.WebViewUtil;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -135,16 +136,7 @@ public class WebViewFragment extends Fragment implements BlockingStep {
|
|||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
||||
webView.loadUrl(
|
||||
"javascript:(function() {" +
|
||||
"if(!document.querySelectorAll('meta[name=viewport]').length) {"+
|
||||
"let meta=document.createElement('meta');"+
|
||||
"meta.name='viewport';"+
|
||||
"meta.content='width=device-width, initial-scale=1';"+
|
||||
"document.head.appendChild(meta);"+
|
||||
"}"+
|
||||
"})()"
|
||||
);
|
||||
WebViewUtil.injectViewport(view);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<!-- Context: Get Started menu -->
|
||||
<string name="show_get_started" comment="Show the "Get Started" menu on startup">Zeige \"%1$s\" beim Start</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages"> Um Tastaturpakete zu installieren, braucht Keyman die Berechtigung zum Lesen/Schreiben.</string>
|
||||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages"> Um Tastaturpakete zu installieren, braucht Keyman die Berechtigung zum Lesen von externem Speicher.</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted"> Zugriff auf den Speicher wurde abgelehnt. Möglicherweise funktioniert die Installation des Tastaturpakets nicht korrekt</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
|
|
@ -56,6 +56,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="install_keyboard_or_dictionary" comment="Menu item to install keyboard or dictionary">Tastatur oder Wörterbuch installieren</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="change_display_language" comment="Menu action to change interface language">Anzeigesprache ändern</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner" comment="text suggestions banner">Banner immer anzeigen</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">Nicht implementiert</string>
|
||||
|
|
@ -108,6 +110,8 @@
|
|||
<!-- Context: KMP Package strings -->
|
||||
<string name="install_predictive_text_package" comment="Title to install dictionary package">Wörterbuch installieren</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="not_valid_package_file" comment="Notification when invalid package file cannot be installed">%1$s ist keine gültige Keyman Paketdatei.\n%2$s\"</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_touch_keyboards_to_install" comment="Notification when no touch-optimized keyboards can be installed">Tastaturpaket enthält keine berührungsoptimierte Tastatur, die installiert werden könnte</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_predictive_text_to_install" comment="Notification when no dictionaries can be installed">Kein neues Wörterbuch zu installieren</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.KeyboardPickerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" >
|
||||
|
|
@ -29,12 +30,6 @@
|
|||
android:label=""
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.KMHelpFileActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.ModelPickerActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
|
|
@ -47,6 +42,18 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" >
|
||||
</activity>
|
||||
|
||||
<!-- Put other WebViewActivities in a separate process so the Keyboard WebView doesn't lag.
|
||||
Ref https://stackoverflow.com/questions/40650643/timed-out-waiting-on-iinputcontextcallback-with-custom-keyboard-on-android -->
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.KMHelpFileActivity"
|
||||
android:process=":KMHelpFileActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import android.content.ContextWrapper;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
|
@ -18,6 +20,21 @@ import com.tavultesoft.kmea.util.ContextUtils;
|
|||
import java.util.Locale;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
static ContextWrapper localeUpdatedContext;
|
||||
|
||||
/**
|
||||
* Some classes aren't an AppCompatActivity and need this helper to localize Toast notifications
|
||||
* in the updated locale.
|
||||
* @param defaultContext - the context to fallback if localeUpdatedContext is null
|
||||
* @param resID - resource ID of the string
|
||||
* @param duration - length of the Toast notification (Toast.LENGTH_LONG or Toast.LENGTH_SHORT)
|
||||
* @param args - optional format parameters for the string
|
||||
*/
|
||||
public static void makeToast(Context defaultContext, int resID, int duration, Object... args) {
|
||||
Context context = (localeUpdatedContext != null) ? localeUpdatedContext : defaultContext;
|
||||
String msg = context.getString(resID);
|
||||
Toast.makeText(context, String.format(msg, args), duration).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
|
|
@ -36,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||
} else {
|
||||
localeToSwitchTo = Locale.forLanguageTag(languageTag);
|
||||
}
|
||||
ContextWrapper localeUpdatedContext = ContextUtils.updateLocale(newBase, localeToSwitchTo);
|
||||
this.localeUpdatedContext = ContextUtils.updateLocale(newBase, localeToSwitchTo);
|
||||
super.attachBaseContext(localeUpdatedContext);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.webkit.WebChromeClient;
|
||||
|
|
@ -29,6 +31,7 @@ import com.tavultesoft.kmea.packages.PackageProcessor;
|
|||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
import com.tavultesoft.kmea.util.HelpFile;
|
||||
import com.tavultesoft.kmea.util.WebViewUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ public class KMHelpFileActivity extends BaseActivity {
|
|||
private WebView webView;
|
||||
private Button finishButton;
|
||||
private String pkgID;
|
||||
private static boolean didSetDataDirectorySuffix = false;
|
||||
|
||||
@SuppressLint({"SetJavaScriptEnabled", "InflateParams"})
|
||||
@Override
|
||||
|
|
@ -49,6 +53,16 @@ public class KMHelpFileActivity extends BaseActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
final Context context = this;
|
||||
|
||||
// Different processes in the same application cannot directly share WebView-related data
|
||||
// https://developer.android.com/reference/android/webkit/WebView.html#setDataDirectorySuffix(java.lang.String)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (!didSetDataDirectorySuffix) {
|
||||
String processName = getProcessName();
|
||||
WebView.setDataDirectorySuffix(processName);
|
||||
didSetDataDirectorySuffix = true;
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_help_file_layout);
|
||||
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
|
|
@ -63,7 +77,7 @@ public class KMHelpFileActivity extends BaseActivity {
|
|||
finishButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
finishAfterTransition();
|
||||
overridePendingTransition(0, android.R.anim.fade_out);
|
||||
}
|
||||
});
|
||||
|
|
@ -130,16 +144,7 @@ public class KMHelpFileActivity extends BaseActivity {
|
|||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
||||
webView.loadUrl(
|
||||
"javascript:(function() {" +
|
||||
"if(!document.querySelectorAll('meta[name=viewport]').length) {"+
|
||||
"let meta=document.createElement('meta');"+
|
||||
"meta.name='viewport';"+
|
||||
"meta.content='width=device-width, initial-scale=1';"+
|
||||
"document.head.appendChild(meta);"+
|
||||
"}"+
|
||||
"})()"
|
||||
);
|
||||
WebViewUtil.injectViewport(view);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -162,14 +167,22 @@ public class KMHelpFileActivity extends BaseActivity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
WebViewUtil.cleanup(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView != null && webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
// Dismiss the help file
|
||||
super.onBackPressed();
|
||||
finishAndRemoveTask();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import org.json.JSONArray;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.data.Keyboard;
|
||||
import com.tavultesoft.kmea.data.KeyboardController;
|
||||
import com.tavultesoft.kmea.KMManager.KeyboardType;
|
||||
|
|
@ -154,10 +155,7 @@ final class KMKeyboard extends WebView {
|
|||
// TODO: Fix base error rather than trying to ignore it "No keyboard stubs exist"
|
||||
|
||||
if ((cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) && (!cm.message().startsWith("No keyboard stubs exist"))) {
|
||||
Toast.makeText(context, "Fatal Error with " + currentKeyboard +
|
||||
". Loading default keyboard", Toast.LENGTH_LONG).show();
|
||||
|
||||
// Still send log about falling back to default keyboard (ignore language ID)
|
||||
// Make Toast notification of error and send log about falling back to default keyboard (ignore language ID)
|
||||
sendError(packageID, keyboardID, "");
|
||||
Keyboard firstKeyboard = KeyboardController.getInstance().getKeyboardInfo(0);
|
||||
if (firstKeyboard != null) {
|
||||
|
|
@ -590,11 +588,13 @@ final class KMKeyboard extends WebView {
|
|||
|
||||
}
|
||||
|
||||
// Display Toast notification that keyboard selection failed, so loading default keyboard.
|
||||
// Also sends a message to Sentry
|
||||
// Display localized Toast notification that keyboard selection failed, so loading default keyboard.
|
||||
// Also sends a message to Sentry (not localized)
|
||||
private void sendError(String packageID, String keyboardID, String languageID) {
|
||||
String msg = String.format("Can't set %s::%s for %s language. Loading default keyboard", packageID, keyboardID, languageID);
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
BaseActivity.makeToast(context, R.string.fatal_keyboard_error, Toast.LENGTH_LONG, packageID, keyboardID, languageID);
|
||||
|
||||
// Don't localize msg for Sentry
|
||||
String msg = String.format(context.getString(R.string.fatal_keyboard_error), packageID, keyboardID, languageID);
|
||||
Sentry.captureMessage(msg);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.R;
|
||||
import com.tavultesoft.kmea.util.DownloadFileUtils;
|
||||
|
||||
|
|
@ -158,8 +159,7 @@ public class CloudApiTypes {
|
|||
|
||||
if (filename == null || filename.isEmpty() || cachedFile == null || !cachedFile.exists()) {
|
||||
// failed to retrieve downloaded file
|
||||
String message = context.getString(R.string.failed_to_retrieve_file);
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
BaseActivity.makeToast(context, R.string.failed_to_retrieve_file, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
return cachedFile;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KeyboardPickerActivity;
|
||||
import com.tavultesoft.kmea.R;
|
||||
import com.tavultesoft.kmea.cloud.CloudApiTypes;
|
||||
|
|
@ -110,7 +111,7 @@ public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Data
|
|||
|
||||
private JSONArray ensureInit(Context aContext,Dataset aDataSet, JSONArray json) {
|
||||
if (json == null && aDataSet.isEmpty()) {
|
||||
Toast.makeText(context, "Failed to access Keyman server!", Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.cannot_connect, Toast.LENGTH_SHORT);
|
||||
handleDownloadError();
|
||||
return null;
|
||||
}
|
||||
|
|
@ -120,7 +121,7 @@ public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Data
|
|||
|
||||
private JSONObject ensureInit(Context aContext,Dataset aDataSet, JSONObject json) {
|
||||
if (json == null && aDataSet.isEmpty()) {
|
||||
Toast.makeText(context, "Failed to access Keyman server!", Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.cannot_connect, Toast.LENGTH_SHORT);
|
||||
handleDownloadError();
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KeyboardEventHandler;
|
||||
import com.tavultesoft.kmea.R;
|
||||
|
|
@ -107,9 +108,7 @@ public class CloudKeyboardPackageDownloadCallback implements ICloudDownloadCallb
|
|||
@Override
|
||||
public void applyCloudDownloadToModel(Context aContext, Void aModel, CloudKeyboardDownloadReturns aCloudResult)
|
||||
{
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.keyboard_download_finished),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.keyboard_download_finished, Toast.LENGTH_SHORT);
|
||||
|
||||
if(aCloudResult.installedResource != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.content.Context;
|
|||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.R;
|
||||
import com.tavultesoft.kmea.cloud.CloudApiTypes;
|
||||
import com.tavultesoft.kmea.cloud.CloudDataJsonUtil;
|
||||
|
|
@ -80,8 +81,7 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
|
|||
@Override
|
||||
public void applyCloudDownloadToModel(Context aContext, Void aModel, List<CloudLexicalModelMetaDataDownloadCallback.MetaDataResult> aCloudResult) {
|
||||
if (aCloudResult.isEmpty()) {
|
||||
String msg = aContext.getString(R.string.catalog_unavailable);
|
||||
Toast.makeText(aContext, msg, Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.catalog_unavailable, Toast.LENGTH_SHORT);
|
||||
KMLog.LogError(TAG, "Could not reach server");
|
||||
return;
|
||||
}
|
||||
|
|
@ -104,16 +104,12 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
|
|||
if(_r.returnjson.target== CloudApiTypes.ApiTarget.KeyboardLexicalModels) {
|
||||
if( CloudDownloadMgr.getInstance().alreadyDownloadingData(_r.additionalDownloadid))
|
||||
{
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.dictionary_download_is_running_in_background),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.dictionary_download_is_running_in_background, Toast.LENGTH_SHORT);
|
||||
continue;
|
||||
}
|
||||
CloudLexicalPackageDownloadCallback _callback = new CloudLexicalPackageDownloadCallback();
|
||||
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.dictionary_download_start_in_background),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.dictionary_download_start_in_background, Toast.LENGTH_SHORT);
|
||||
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(aContext,
|
||||
_r.additionalDownloadid, null, _callback,
|
||||
|
|
@ -152,8 +148,7 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
|
|||
KMLog.LogException(TAG, "Error parsing lexical model from api.keyman.com. ", e);
|
||||
}
|
||||
} else {
|
||||
String msg = aContext.getString(R.string.no_associated_model);
|
||||
Toast.makeText(aContext, msg, Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.no_associated_model, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KeyboardEventHandler;
|
||||
import com.tavultesoft.kmea.R;
|
||||
|
|
@ -98,9 +99,7 @@ public class CloudLexicalPackageDownloadCallback implements ICloudDownloadCallba
|
|||
@Override
|
||||
public void applyCloudDownloadToModel(Context aContext, Void aModel, CloudKeyboardDownloadReturns aCloudResult)
|
||||
{
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.dictionary_download_finished),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(aContext, R.string.dictionary_download_finished, Toast.LENGTH_SHORT);
|
||||
|
||||
if(aCloudResult.installedResource != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.BuildConfig;
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
|
|
@ -347,8 +348,7 @@ public class CloudRepository {
|
|||
preCacheDataSet(context,null,null,null);
|
||||
|
||||
if(CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
|
||||
String msg = context.getString(R.string.catalog_download_is_running_in_background);
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.catalog_download_is_running_in_background, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
return memCachedDataset;
|
||||
|
|
@ -394,12 +394,10 @@ public class CloudRepository {
|
|||
cloudQueries.toArray(params);
|
||||
|
||||
if (CloudDownloadMgr.getInstance().alreadyDownloadingData(DOWNLOAD_IDENTIFIER_CATALOGUE)) {
|
||||
String msg = context.getString(R.string.catalog_download_is_running_in_background);
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.catalog_download_is_running_in_background, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
updateIsRunning = true;
|
||||
String msg = context.getString(R.string.catalog_download_start_in_background);
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(context, R.string.catalog_download_start_in_background, Toast.LENGTH_SHORT);
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.core.app.NotificationCompat;
|
|||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KeyboardPickerActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
|
|
@ -105,7 +106,7 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(currentContext, currentContext.getString(R.string.update_check_current), Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(currentContext, R.string.update_check_current, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
SharedPreferences prefs = currentContext.getSharedPreferences(currentContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
|
@ -121,7 +122,7 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(currentContext, currentContext.getString(R.string.update_check_unavailable), Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(currentContext, R.string.update_check_unavailable, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
updateCheckFailed = true;
|
||||
checkingUpdates = false;
|
||||
|
|
@ -193,7 +194,7 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
currentContext.startActivity(intent);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(currentContext, "No internet connection", Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(currentContext, R.string.cannot_connect, Toast.LENGTH_SHORT);
|
||||
checkingUpdates = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -554,12 +555,12 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
|
||||
if (failedUpdateCount > 0) {
|
||||
Toast.makeText(currentContext, "One or more resources failed to update!", Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(currentContext, R.string.update_failed, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
updateFailed = true;
|
||||
checkingUpdates = false;
|
||||
} else {
|
||||
Toast.makeText(currentContext, "Resources successfully updated!", Toast.LENGTH_SHORT).show();
|
||||
BaseActivity.makeToast(currentContext, R.string.update_success, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
SharedPreferences prefs = currentContext.getSharedPreferences(currentContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.tavultesoft.kmea.BaseActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
import com.tavultesoft.kmea.R;
|
||||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
|
||||
|
|
@ -47,8 +49,11 @@ public final class HelpFile {
|
|||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
} catch (NullPointerException e) {
|
||||
String message = "FileProvider undefined in app to load" + customHelp.toString();
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
// Localize Toast error
|
||||
BaseActivity.makeToast(context, R.string.fileprovider_undefined, Toast.LENGTH_LONG);
|
||||
|
||||
// Don't localize message to Sentry
|
||||
String message = String.format(context.getString(R.string.fileprovider_undefined), customHelp.toString());
|
||||
KMLog.LogException(TAG, message, e);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Copyright (C) 2021 SIL International. All rights reserved.
|
||||
*/
|
||||
package com.tavultesoft.kmea.util;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public final class WebViewUtil {
|
||||
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
||||
public static void injectViewport(WebView webView) {
|
||||
if (webView != null) {
|
||||
webView.loadUrl(
|
||||
"javascript:(function() {" +
|
||||
"if(document.head && !document.querySelectorAll('meta[name=viewport]').length) {"+
|
||||
"let meta=document.createElement('meta');"+
|
||||
"meta.name='viewport';"+
|
||||
"meta.content='width=device-width, initial-scale=1';"+
|
||||
"document.head.appendChild(meta);"+
|
||||
"}"+
|
||||
"})()"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Blank the webView and destroy completely
|
||||
// Reference: https://stackoverflow.com/questions/17418503/destroy-webview-in-android/17458577#17458577
|
||||
public static void cleanup(WebView webView) {
|
||||
if (webView != null) {
|
||||
webView.loadUrl("about:blank");
|
||||
ViewGroup viewGroup = (ViewGroup) webView.getParent();
|
||||
if (viewGroup != null) {
|
||||
viewGroup.removeView(webView);
|
||||
}
|
||||
webView.removeAllViews();
|
||||
webView.destroy();
|
||||
webView = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[neu] %1$s</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_qr_code" comment="QR Code description"> Scannen Sie diesen Code, um diese\nTastatur auf einem anderen Gerät zu laden</string>
|
||||
<!-- Context: KMP Package welcome.htm title -->
|
||||
<!-- Context: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Willkommen bei %1$s</string>
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Suche nach zugehörigem Wörterbuch zum Herunterladen</string>
|
||||
|
|
@ -91,6 +91,8 @@
|
|||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">Das ausgewählte Wörterbuch wird bereits heruntergeladen. Bitte versuchen Sie es gleich nochmal!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">Download des Wörterbuchs ist abgeschlossen.</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Laden der heruntergeladenen Datei fehlgeschlagen</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Fehler beim Zugriff auf den Server!</string>
|
||||
<!-- Context: General Updates -->
|
||||
|
|
@ -123,6 +125,8 @@
|
|||
<item quantity="one">(%1$d Tastatur)</item>
|
||||
<item quantity="other">(%1$d Tastaturen)</item>
|
||||
</plurals>
|
||||
<!-- Context: "Change Display Language" menu -->
|
||||
<string name="default_locale" comment="Use the device's current locale">Standardsprache</string>
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Popup menu labels -->
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@
|
|||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">Le dictionnaire sélectionné est déjà en cours de téléchargement ; veuillez réessayer dans un instant !</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">Téléchargement du dictionnaire terminé.</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="failed_to_access_downloaded_file" comment="Failed to access downloaded file">Impossible d\'accéder au fichier téléchargé</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Impossible de récupérer le fichier téléchargé</string>
|
||||
<!-- Context: General Updates -->
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">\nCannot connect to Keyman server!\n</string>
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">Cannot connect to Keyman server!</string>
|
||||
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_delete_keyboard" comment="Confirmation to delete a keyboard">Would you like to delete this keyboard?</string>
|
||||
|
|
@ -106,6 +106,13 @@
|
|||
<!-- Context: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Welcome to %1$s</string>
|
||||
|
||||
<!-- Context: Keyboard app doesn't include FileProvider library needed to view help file -->
|
||||
<string name="fileprovider_undefined" comment="FileProvider library needed to view help file">
|
||||
FileProvider library needed to view help file: %1$s</string>
|
||||
|
||||
<!-- Context: Fatal keyboard error. Will load default keyboard -->
|
||||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard">
|
||||
Fatal keyboard error with %1$s:%2$s for %3$s language. Loading default keyboard.</string>
|
||||
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Checking for associated dictionary to download</string>
|
||||
|
|
@ -148,8 +155,8 @@
|
|||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">Dictionary download is finished.</string>
|
||||
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="failed_to_access_downloaded_file" comment="Failed to access downloaded file">Failed to access downloaded file</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">Download failed</string>
|
||||
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Failed to retrieve downloaded file</string>
|
||||
|
|
@ -160,6 +167,11 @@
|
|||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"All resources are up to date!"</string>
|
||||
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">One or more resources failed to update!</string>
|
||||
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">Resources successfully updated!</string>
|
||||
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">Dictionary version</string>
|
||||
|
|
|
|||
|
|
@ -17,4 +17,8 @@ Here are some of the new features we have added to Keyman for Android 14.0:
|
|||
* Match user input capital letters when offering suggestions (#3845)
|
||||
* Update minimum Android SDK to 21 (Android 5.0 Lollipop) (#2993)
|
||||
* Keyman now works more reliably with WeChat and Telegram (#4254)
|
||||
* Added new menu to change the display language (#4261)
|
||||
* Added new Settings menu to [Change Display Language](../basic/config/index#Change-Display-Language): (#4261)
|
||||
* French
|
||||
* German
|
||||
* Khmer
|
||||
* Obolo
|
||||
|
|
|
|||
1
common/core/desktop/.gitignore
vendored
1
common/core/desktop/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
.vs/
|
||||
build*/
|
||||
configure/
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ Maintainer: Debian Input Method Team <debian-input-method@lists.debian.org>
|
|||
Uploaders:
|
||||
Daniel Glassey <wdg@debian.org>,
|
||||
Keyman team <support@keyman.com>,
|
||||
Eberhard Beilharz <eb1@sil.org>,
|
||||
Build-Depends:
|
||||
debhelper (>= 11),
|
||||
meson (>= 0.45),
|
||||
ninja-build,
|
||||
Standards-Version: 4.5.0
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://github.com/keymanapp/keyman.git
|
||||
Vcs-Browser: https://github.com/keymanapp/keyman/tree/master/common/core/desktop
|
||||
Homepage: https://www.keyman.com
|
||||
|
|
|
|||
51
common/core/desktop/debian/upstream/signing-key.asc
Normal file
51
common/core/desktop/debian/upstream/signing-key.asc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=jcZL
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# How to build the keyboard processor
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3
|
||||
- Meson build system.
|
||||
- C++14 or later compiler (VC++ 2017 or later for Windows).
|
||||
|
|
@ -8,46 +9,55 @@
|
|||
- kmcomp (for tests) -- must be added to path
|
||||
|
||||
## Installing Python3
|
||||
|
||||
### Linux
|
||||
|
||||
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.
|
||||
|
||||
### Mac OS X
|
||||
|
||||
You can get the official installer from the official Python site:
|
||||
[https://www.python.org/downloads/mac-osx/]()
|
||||
<https://www.python.org/downloads/mac-osx/>
|
||||
|
||||
### Windows
|
||||
You can get the official installer from the official Python site:
|
||||
[https://www.python.org/downloads/windows/]()
|
||||
|
||||
You can get the official installer from the official Python site:
|
||||
<https://www.python.org/downloads/windows/>
|
||||
|
||||
## Installing Meson
|
||||
|
||||
Ensure you have Python3 correctly installed and can run the command `pip3`.
|
||||
```
|
||||
|
||||
```bash
|
||||
python3 -m pip install meson
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Building on Windows
|
||||
|
||||
For Windows, use `build.bat` -- this handles environment and x86/x64
|
||||
cross-compiles with Visual Studio 2017+.
|
||||
|
||||
You may need to set `SDKVER` environement variable to the current
|
||||
Windows SDK version, if it cannot be automatically detected, e.g.:
|
||||
|
||||
```
|
||||
```DOS
|
||||
set SDKVER=10.0.19041.0
|
||||
```
|
||||
|
||||
To build:
|
||||
```
|
||||
|
||||
```DOS
|
||||
build.bat all
|
||||
```
|
||||
|
||||
### Building on Linux, macOS
|
||||
|
||||
For all other platforms, in your source directory do the following:
|
||||
```
|
||||
|
||||
```bash
|
||||
cd desktop
|
||||
meson build --werror
|
||||
cd build
|
||||
|
|
@ -55,24 +65,29 @@ ninja
|
|||
meson test
|
||||
```
|
||||
|
||||
For a debug build, pass `--buildtype debug` to meson.
|
||||
|
||||
## Note on kmcomp
|
||||
kmcomp is the command-line compiler from Keyman Developer, available from https://keyman.com/ or
|
||||
in this repo in /windows/src/developer/kmcomp. The compiler is currently available as a Windows
|
||||
|
||||
kmcomp is the command-line compiler from Keyman Developer, available from <https://keyman.com/> or
|
||||
in this repo in `/windows/src/developer/kmcomp`. The compiler is currently available as a Windows
|
||||
PE executable only, but it does run under WINE.
|
||||
|
||||
## Additional configuration notes
|
||||
|
||||
### Windows
|
||||
|
||||
The search path can be edited through System settings / Advanced system settings /
|
||||
Environment Variables / User environment variables.
|
||||
|
||||
If you have Keyman Developer installed, add %KeymanDeveloperPath% to your path. Otherwise, add
|
||||
If you have Keyman Developer installed, add `%KeymanDeveloperPath%` to your path. Otherwise, add
|
||||
the path where you extracted the kmcomp archive.
|
||||
|
||||
### Linux
|
||||
|
||||
You need a wrapper `kmcomp` shell script:
|
||||
|
||||
```
|
||||
```bash
|
||||
#!/bin/bash
|
||||
wine `dirname "$0"`/kmcomp.exe "$@"
|
||||
```
|
||||
|
|
@ -81,4 +96,5 @@ Place this in the same folder as you extracted kmcomp.exe, and `chmod +x kmcomp`
|
|||
to the path (e.g. `export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`)
|
||||
|
||||
### macOS
|
||||
|
||||
TODO
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Keyman Keyboard Processor API
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Cross platform.
|
||||
2. Cross language.
|
||||
3. Facilitate stateless operation of the Engine.
|
||||
|
|
@ -9,8 +10,8 @@
|
|||
6. Support querying Keyboard attributes.
|
||||
7. Idempotent
|
||||
|
||||
|
||||
## Design decisions in support of requirements:
|
||||
|
||||
- Use C or C99 types and calling convention for the interface, it has the
|
||||
broadest language FFI support. [1,2]
|
||||
- Have client (platform glue) code load keyboards, manage & pass state. [3,4,7]
|
||||
|
|
@ -18,8 +19,8 @@
|
|||
engine [5,6]
|
||||
- Provide get/set calls for client accessible keyboard state information [3,4]
|
||||
|
||||
|
||||
## Glossary
|
||||
|
||||
- __Platform layer:__
|
||||
the code that consumes the Keyman Keyboard Processor API, and provides the
|
||||
operating system-specific handling of keystroke events and integration with
|
||||
|
|
@ -29,22 +30,32 @@ the application that has the focus and receives text events from the Platform
|
|||
layer.
|
||||
- __Context:__ Text preceding the insertion point
|
||||
- __Marker:__ Positional state that can be placed in the Context.
|
||||
- __Keyboard:__ A set of rules for execution my an Engine
|
||||
- __Keyboard:__ A set of rules for execution by an Engine
|
||||
- __Option:__ A variable in a dynamic or static key value store.
|
||||
- __Processor:__
|
||||
The component that implements this API and can parse and execute a particular
|
||||
keyboard.
|
||||
- __State:__ An object that hold internal state of the Processor for a given
|
||||
- __State:__ An object that holds internal state of the Processor for a given
|
||||
insertion point
|
||||
- __Action:__
|
||||
A directive output by the processor detailing how the Platform layer should
|
||||
transform the Client Application's text buffer. There may be several items
|
||||
produced by a single keyboard event.
|
||||
- __Keyboard Event:__
|
||||
A virtual key board event and modifier map recevied from the platform to be
|
||||
A virtual key board event and modifier map recevied from the Platform layer to be
|
||||
processed with the state object for this Client application.
|
||||
|
||||
- __Virtual Key:__
|
||||
A code based on the US English layout, with values matching the Windows
|
||||
virtual key codes. See `keyboardprocessor_vkeys.h` for definitions.
|
||||
- __Modifier Key:__
|
||||
The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may
|
||||
have other names (e.g. Alt is called Option on macOS); other platform-specific
|
||||
modifiers such as Windows key are excluded from this set. Some modifiers are
|
||||
transient, such as Control, and others have long-lasting state, such as
|
||||
Caps Lock.
|
||||
|
||||
## API
|
||||
|
||||
### Namespace
|
||||
|
||||
All calls, types and enums are prefixed with the namespace identifier `km_kbp_`
|
||||
|
|
|
|||
|
|
@ -41,23 +41,23 @@ The application that has the focus and receives text events from the Platform
|
|||
layer.
|
||||
- __Context:__ Text preceding the insertion point
|
||||
- __Marker:__ Positional state that can be placed in the Context.
|
||||
- __Keyboard:__ A set of rules for execution my an Engine
|
||||
- __Keyboard:__ A set of rules for execution by an Engine
|
||||
- __Option:__ A variable in a dynamic or static key value store.
|
||||
- __Processor:__
|
||||
The component that implements this API and can parse and execute a particular
|
||||
keyboard.
|
||||
- __State:__ An object that hold internal state of the Processor for a given
|
||||
- __State:__ An object that holds internal state of the Processor for a given
|
||||
insertion point
|
||||
- __Action:__
|
||||
A directive output by the processor detailing how the Platform layer should
|
||||
transform the Client Application's text buffer. There may be several items
|
||||
produced by a single keyboard event.
|
||||
- __Keyboard Event:__
|
||||
A virtual key event and modifier map received from the platform to be
|
||||
A virtual key event and modifier map received from the Platform layer to be
|
||||
processed with the state object for this Client application.
|
||||
- __Virtual Key:__
|
||||
A code based on the US English layout, with values matching the Windows
|
||||
virtual key codes. See keyboardprocessor_vkeys.h for definitions.
|
||||
virtual key codes. See `keyboardprocessor_vkeys.h` for definitions.
|
||||
- __Modifier Key:__
|
||||
The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may
|
||||
have other names (e.g. Alt is called Option on macOS); other platform-specific
|
||||
|
|
|
|||
|
|
@ -54,25 +54,29 @@ int km::kbp::kmx::DebugLog_1(const char *file, int line, const char *function, c
|
|||
fmtbuf[255] = 0;
|
||||
va_end(vars);
|
||||
|
||||
if(g_debug_KeymanLog) {
|
||||
if(g_debug_ToConsole) { // I3951
|
||||
char windowinfo[1024];
|
||||
sprintf(windowinfo,
|
||||
"%ld" TAB //"TickCount" TAB
|
||||
"%s:%d" TAB //"SourceFile" TAB
|
||||
"%s" TAB //"Function"
|
||||
"%s" NL, //"Message"
|
||||
if (!g_debug_KeymanLog)
|
||||
return 0;
|
||||
|
||||
GetTickCount(), //"TickCount" TAB
|
||||
file, line, //"SourceFile" TAB
|
||||
function, //"Function" TAB
|
||||
fmtbuf); //"Message"
|
||||
char windowinfo[1024];
|
||||
sprintf(windowinfo,
|
||||
"%ld" TAB //"TickCount" TAB
|
||||
"%s:%d" TAB //"SourceFile" TAB
|
||||
"%s" TAB //"Function"
|
||||
"%s" NL, //"Message"
|
||||
|
||||
GetTickCount(), //"TickCount" TAB
|
||||
file, line, //"SourceFile" TAB
|
||||
function, //"Function" TAB
|
||||
fmtbuf); //"Message"
|
||||
|
||||
if (g_debug_ToConsole) { // I3951
|
||||
std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo);
|
||||
} else {
|
||||
#ifdef _USE_WINDOWS
|
||||
std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo);
|
||||
std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo);
|
||||
#else
|
||||
syslog(LOG_DEBUG, "%s", windowinfo);
|
||||
syslog(LOG_DEBUG, "%s", windowinfo);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -125,7 +129,8 @@ const char *km::kbp::kmx::Debug_UnicodeString(PKMX_WCHAR s, int x) {
|
|||
bufout[x][0] = 0;
|
||||
for (p = s, q = bufout[x]; *p && (p - s < 128); p++)
|
||||
{
|
||||
sprintf(q, "U+%4.4X ", *p); q = strchr(q, 0);
|
||||
sprintf(q, "U+%4.4X ", *p);
|
||||
q = strchr(q, 0);
|
||||
}
|
||||
//WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL);
|
||||
return bufout[x];
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKe
|
|||
LPKEYBOARD kbp;
|
||||
PKMX_BYTE filebase;
|
||||
|
||||
DebugLog("Loading file %ws", fileName);
|
||||
DebugLog("Loading file '%s'", fileName);
|
||||
if(!fileName || !lpKeyboard)
|
||||
{
|
||||
DebugLog("Bad Filename");
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ using namespace kmx;
|
|||
|
||||
/* Globals */
|
||||
|
||||
KMX_BOOL km::kbp::kmx::g_debug_ToConsole = TRUE, km::kbp::kmx::g_debug_KeymanLog = TRUE;
|
||||
KMX_BOOL km::kbp::kmx::g_debug_ToConsole = FALSE;
|
||||
KMX_BOOL km::kbp::kmx::g_debug_KeymanLog = TRUE;
|
||||
KMX_BOOL km::kbp::kmx::g_silent = FALSE;
|
||||
|
||||
/*
|
||||
|
|
@ -120,7 +121,7 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
PKMX_WCHAR p;
|
||||
int sdmfI;
|
||||
|
||||
/*
|
||||
/*
|
||||
If the number of nested groups goes higher than 50, then break out - this is
|
||||
a limitation of stack size. This is basically a catch-all for freaky apps that
|
||||
cause message loopbacks and nasty things like that. Okay, it's really a catch all
|
||||
|
|
|
|||
|
|
@ -128,10 +128,8 @@ const char *Debug_UnicodeString(PKMX_WCHAR s, int x = 0);
|
|||
const char *Debug_UnicodeString(std::u16string s, int x = 0);
|
||||
const char *Debug_ModifierName(KMX_UINT modifiers);
|
||||
|
||||
//inline KMX_BOOL ShouldDebug();
|
||||
|
||||
inline KMX_BOOL ShouldDebug() {
|
||||
return TRUE; // g_debug_KeymanLog;
|
||||
return g_debug_KeymanLog;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ PKMX_WCHAR km::kbp::kmx::incxstr(PKMX_WCHAR p)
|
|||
case CODE_INDEX: return p+2;
|
||||
case CODE_USE: return p+1;
|
||||
case CODE_DEADKEY: return p+1;
|
||||
case CODE_EXTENDED: p += 2; while(*p != UC_SENTINEL_EXTENDEDEND) p++; return p+1;
|
||||
case CODE_EXTENDED: p += 2; while(*p && *p != UC_SENTINEL_EXTENDEDEND) p++; return p+1;
|
||||
case CODE_CLEARCONTEXT: return p+1;
|
||||
case CODE_CALL: return p+1;
|
||||
case CODE_CONTEXTEX: return p+1;
|
||||
|
|
|
|||
62
common/core/desktop/tests/unit/kmx/README.md
Normal file
62
common/core/desktop/tests/unit/kmx/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# KMX unit tests
|
||||
|
||||
This directory contains unit tests that test the KMX processing.
|
||||
|
||||
The driver (`kmx.cpp`) loads and executes the tests. Each test consist of a
|
||||
Keyman keyboard source file (`0*.kmn`) and the compiled KMX keyboard.
|
||||
The source file contains rules and defines the setup, input and expected results.
|
||||
|
||||
## Defining tests in the `.kmn` file
|
||||
|
||||
The top of the file contains comments that define the setup, input and expected results.
|
||||
Following the comments are the normal rules of a Keyman keyboard.
|
||||
|
||||
### Setup
|
||||
|
||||
- **description**: this is just for commenting what we test here
|
||||
|
||||
```text
|
||||
c Description: Tests Caps Lock env set
|
||||
```
|
||||
|
||||
- **option**: allows to put the system in a defined state. The example below turns
|
||||
caps-lock off.
|
||||
|
||||
```text
|
||||
c option: &capsLock=0
|
||||
```
|
||||
|
||||
- **context**: allows to setup a string which serves as context
|
||||
|
||||
```text
|
||||
c context:
|
||||
```
|
||||
|
||||
### Input
|
||||
|
||||
- **keys**: defines one or more virtual keys that will be processed one after the
|
||||
other
|
||||
|
||||
```text
|
||||
c keys: [K_1][K_CAPS][K_2][SHIFT K_3][K_4][K_CAPS][K_5][K_CAPS][K_6]
|
||||
```
|
||||
|
||||
### Expected Result
|
||||
|
||||
- **expected**: the resulting string of processing the input keys. In the example
|
||||
below each key press is expected to output the string `'pass.'`.
|
||||
|
||||
```text
|
||||
c expected: pass.pass.pass.pass.pass.pass.
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
All tests can be run at once with `meson test`.
|
||||
|
||||
Alternatively it's possible to run a single test with:
|
||||
|
||||
```bash
|
||||
cd common/core/desktop
|
||||
build/tests/unit/kmx/kmx 'tests/unit/kmx/038 - punctkeys.kmn' 'tests/unit/kmx/038 - punctkeys.kmx'
|
||||
```
|
||||
|
|
@ -510,5 +510,6 @@ int main(int argc, char *argv[])
|
|||
return 1;
|
||||
}
|
||||
|
||||
km::kbp::kmx::g_debug_ToConsole = TRUE;
|
||||
return run_test(argv[1], argv[2]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,10 @@ namespace com.keyman.text {
|
|||
* Handles default output and keyboard processing for both OSK and physical keystrokes.
|
||||
*
|
||||
* @param {Object} e The abstracted KeyEvent to use for keystroke processing
|
||||
* @returns {Object} A RuleBehavior object describing the cumulative effects of
|
||||
* all matched keyboard rules.
|
||||
*/
|
||||
processKeyEvent(keyEvent: KeyEvent) {
|
||||
processKeyEvent(keyEvent: KeyEvent): RuleBehavior {
|
||||
let formFactor = keyEvent.device.formFactor;
|
||||
|
||||
// Determine the current target for text output and create a "mock" backup
|
||||
|
|
@ -70,14 +72,14 @@ namespace com.keyman.text {
|
|||
// If it's a desktop OSK style and this triggers a layer change,
|
||||
// a modifier key was clicked. No output expected, so it's safe to instantly exit.
|
||||
if(this.keyboardProcessor.selectLayer(keyEvent)) {
|
||||
return true;
|
||||
return new RuleBehavior();
|
||||
}
|
||||
}
|
||||
|
||||
// Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version
|
||||
// of state management.
|
||||
if(!fromOSK && this.keyboardProcessor.doModifierPress(keyEvent, !fromOSK)) {
|
||||
return true;
|
||||
return new RuleBehavior();
|
||||
}
|
||||
|
||||
// If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke.
|
||||
|
|
@ -88,10 +90,10 @@ namespace com.keyman.text {
|
|||
|
||||
// Can the suggestion UI revert a recent suggestion? If so, do that and swallow the backspace.
|
||||
if((keyEvent.kName == "K_BKSP" || keyEvent.Lcode == Codes.keyCodes["K_BKSP"]) && this.languageProcessor.tryRevertSuggestion()) {
|
||||
return;
|
||||
return new RuleBehavior();
|
||||
// Can the suggestion UI accept an existing suggestion? If so, do that and swallow the space character.
|
||||
} else if((keyEvent.kName == "K_SPACE" || keyEvent.Lcode == Codes.keyCodes["K_SPACE"]) && this.languageProcessor.tryAcceptSuggestion('space')) {
|
||||
return;
|
||||
return new RuleBehavior();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,15 +173,6 @@ namespace com.keyman.text {
|
|||
}
|
||||
}
|
||||
|
||||
/* I732 END - 13/03/2007 MCD: End Positional Layout support in OSK */
|
||||
|
||||
// TODO: rework the return value to be `ruleBehavior` instead. Functions that call this one are
|
||||
// the ones that should worry about event handler returns, etc. Not this one.
|
||||
//
|
||||
// They should also be the ones to handle the TODOs seen earlier in this function -
|
||||
// once THOSE are properly relocated. (They're too DOM-heavy to remain in web-core.)
|
||||
|
||||
// Only return true (for the eventual event handler's return value) if we didn't match a rule.
|
||||
return ruleBehavior;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ namespace com.keyman.text.prediction {
|
|||
// the input will be automatically rewound to the preInput state.
|
||||
transform: original.transform,
|
||||
// The ID part is critical; the reversion can't be applied without it.
|
||||
transformId: original.token, // reversions use the additive inverse.
|
||||
transformId: -original.token, // reversions use the additive inverse.
|
||||
displayAs: reversion.displayAs, // The real reason we needed to call the LMLayer.
|
||||
id: reversion.id,
|
||||
tag: reversion.tag
|
||||
|
|
|
|||
|
|
@ -37,15 +37,15 @@ namespace com.keyman.keyboards {
|
|||
shiftKey?: LayoutKey,
|
||||
capsKey?: LayoutKey,
|
||||
numKey?: LayoutKey,
|
||||
scrollKey?: LayoutKey
|
||||
scrollKey?: LayoutKey,
|
||||
aligned?: boolean
|
||||
}
|
||||
|
||||
export type LayoutFormFactor = {
|
||||
"displayUnderlying"?: boolean,
|
||||
"font": string,
|
||||
"layer": LayoutLayer[],
|
||||
keyLabels?: boolean,
|
||||
isDefault?: boolean;
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
export type LayoutSpec = {
|
||||
|
|
@ -172,6 +172,11 @@ namespace com.keyman.keyboards {
|
|||
}
|
||||
}
|
||||
|
||||
// If there is no predefined layout, even touch layouts will follow the desktop's
|
||||
// setting for the displayUnderlying flag. As the desktop layout uses a different
|
||||
// format for its layout spec, that's found at the field referenced below.
|
||||
layout["displayUnderlying"] = !!keyboard.scriptObject['KDU'];
|
||||
|
||||
// For desktop devices, we must create all layers, even if invalid.
|
||||
if(formFactor == 'desktop') {
|
||||
invalidIdList = Layouts.generateLayerIds(chiral);
|
||||
|
|
|
|||
|
|
@ -71,11 +71,6 @@ namespace com.keyman.keyboards {
|
|||
return this.scriptObject['KN'];
|
||||
}
|
||||
|
||||
get displaysUnderlyingKeys(): boolean {
|
||||
// Returns false if undefined or false-like (including 0), true otherwise.
|
||||
return !!this.scriptObject['KDU'];
|
||||
}
|
||||
|
||||
// TODO: Better typing.
|
||||
private get _legacyLayoutSpec(): any {
|
||||
return this.scriptObject['KV']; // used with buildDefaultLayout; layout must be constructed at runtime.
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ namespace com.keyman {
|
|||
Opera: KeyMap = new KeyMap();
|
||||
|
||||
constructor() {
|
||||
//ffie['k109'] = 189; // - // These two number-pad VK rules are *not* correct for more recent FF! JMD 8/11/12
|
||||
//ffie['k107'] = 187; // = // FF 3.0 // I2062
|
||||
// All three have been around since at least May 2014 / FF 29.
|
||||
// It'd hard to find precise history, but at least that much has been confirmed.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode, on Feb 26 2021.
|
||||
this.FF['k61'] = 187; // = // FF 2.0
|
||||
this.FF['k59'] = 186; // ;
|
||||
this.FF['k173'] = 189; // -/_
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ namespace com.keyman.text {
|
|||
let toCaret = this.getDeadkeyCaret();
|
||||
|
||||
// Step 1: Determine the number of left-deletions.
|
||||
for(var newCaret=0; newCaret < fromCaret; newCaret++) {
|
||||
let maxLeftMatch = fromCaret < toCaret ? fromCaret : toCaret;
|
||||
for(var newCaret=0; newCaret < maxLeftMatch; newCaret++) {
|
||||
if(from._kmwCharAt(newCaret) != to._kmwCharAt(newCaret)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -132,8 +133,16 @@ namespace com.keyman.text {
|
|||
|
||||
let undeletedRight = to._kmwLength() - toCaret;
|
||||
let originalRight = from._kmwLength() - fromCaret;
|
||||
let deletedRight = originalRight - undeletedRight;
|
||||
|
||||
return new TextTransform(delta, deletedLeft, originalRight - undeletedRight);
|
||||
// May occur when reverting a suggestion that had been applied mid-word.
|
||||
if(deletedRight < 0) {
|
||||
// Restores deleteRight characters.
|
||||
delta = delta + to._kmwSubstr(toCaret, -deletedRight);
|
||||
deletedRight = 0;
|
||||
}
|
||||
|
||||
return new TextTransform(delta, deletedLeft, deletedRight);
|
||||
}
|
||||
|
||||
buildTranscriptionFrom(original: OutputTarget, keyEvent: KeyEvent, alternates?: Alternate[]): Transcription {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ namespace com.keyman.text {
|
|||
*/
|
||||
export class RuleBehavior {
|
||||
/**
|
||||
* The before-and-after Transform from matching a keyboard rule.
|
||||
* The before-and-after Transform from matching a keyboard rule. May be `null`
|
||||
* if no keyboard rules were matched for the keystroke.
|
||||
*/
|
||||
transcription: Transcription;
|
||||
transcription: Transcription = null;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a BEEP command was issued by the matched keyboard rule.
|
||||
|
|
@ -26,7 +27,7 @@ namespace com.keyman.text {
|
|||
/**
|
||||
* Denotes a non-output default behavior; this should be evaluated later, against the true keystroke.
|
||||
*/
|
||||
triggersDefaultCommand?: boolean;
|
||||
triggersDefaultCommand: boolean = false;
|
||||
|
||||
/**
|
||||
* Denotes error log messages generated when attempting to generate this behavior.
|
||||
|
|
@ -44,6 +45,10 @@ namespace com.keyman.text {
|
|||
predictionPromise?: Promise<Suggestion[]>;
|
||||
|
||||
finalize(processor: KeyboardProcessor) {
|
||||
if(!this.transcription) {
|
||||
throw "Cannot finalize a RuleBehavior with no transcription.";
|
||||
}
|
||||
|
||||
let outputTarget = this.transcription.keystroke.Ltarg;
|
||||
|
||||
if(processor.beepHandler && this.beep) {
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ namespace models {
|
|||
export function applyTransform(transform: Transform, context: Context): Context {
|
||||
// First, get the current context
|
||||
let fullLeftContext = context.left || '';
|
||||
let lLen = fullLeftContext.length;
|
||||
let lLen = fullLeftContext.kmwLength();
|
||||
let lDel = lLen < transform.deleteLeft ? lLen : transform.deleteLeft;
|
||||
|
||||
let leftContext = fullLeftContext.substring(0, lLen - lDel) + (transform.insert || '');
|
||||
let leftContext = fullLeftContext.kmwSubstr(0, lLen - lDel) + (transform.insert || '');
|
||||
|
||||
let fullRightContext = context.right || '';
|
||||
let rLen = fullRightContext.length;
|
||||
let rLen = fullRightContext.kmwLength();
|
||||
let rDel = rLen < transform.deleteRight ? rLen : transform.deleteRight;
|
||||
|
||||
let rightContext = fullRightContext.substring(rDel);
|
||||
let rightContext = fullRightContext.kmwSubstr(rDel);
|
||||
|
||||
return {
|
||||
left: leftContext,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,71 @@ var ModelCompositor = require('../../build/intermediate').ModelCompositor;
|
|||
|
||||
describe('ModelCompositor', function() {
|
||||
describe('Prediction with 14.0+ models', function() {
|
||||
describe('Basic suggestion generation', function() {
|
||||
var plainModel = new TrieModel(jsonFixture('tries/english-1000'),
|
||||
{wordBreaker: wordBreakers.default}
|
||||
);
|
||||
|
||||
it('generates suggestions with expected properties', function() {
|
||||
let compositor = new ModelCompositor(plainModel);
|
||||
let context = {
|
||||
left: 'th', startOfBuffer: true, endOfBuffer: true,
|
||||
};
|
||||
|
||||
let inputTransform = {
|
||||
insert: 'e',
|
||||
deleteLeft: 0
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// Suggstions are built based on the context state BEFORE the triggering
|
||||
// input, replacing the prediction's root with the complete word.
|
||||
//
|
||||
// This is necessary, in part, for proper display-string construction.
|
||||
assert.equal(suggestion.transform.deleteLeft, 2);
|
||||
});
|
||||
|
||||
let keep = suggestions.find(function(suggestion) {
|
||||
return suggestion.tag == 'keep';
|
||||
});
|
||||
|
||||
assert.isDefined(keep);
|
||||
assert.equal(keep.transform.insert, 'the ');
|
||||
|
||||
// Expect an appended space.
|
||||
let expectedEntries = ['they ', 'there ', 'their ', 'these ', 'themselves '];
|
||||
expectedEntries.forEach(function(entry) {
|
||||
assert.isDefined(suggestions.find(function(suggestion) {
|
||||
return suggestion.transform.insert == entry;
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles suggestions after a backspace', function() {
|
||||
let compositor = new ModelCompositor(plainModel);
|
||||
let context = {
|
||||
left: 'the ', startOfBuffer: true, endOfBuffer: true,
|
||||
};
|
||||
|
||||
let inputTransform = {
|
||||
insert: '',
|
||||
deleteLeft: 1
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// Suggestions always delete the full root of the suggestion.
|
||||
//
|
||||
// After a backspace, that means the text 'the' - 3 chars.
|
||||
// Char 4 is for the original backspace, as suggstions are built
|
||||
// based on the context state BEFORE the triggering input -
|
||||
// here, a backspace.
|
||||
assert.equal(suggestion.transform.deleteLeft, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applySuggestionCasing', function() {
|
||||
let plainApplyCasing = function(caseToApply, text) {
|
||||
switch(caseToApply) {
|
||||
|
|
@ -764,7 +829,7 @@ describe('ModelCompositor', function() {
|
|||
|
||||
let baseSuggestion = initialSuggestions[1];
|
||||
let reversion = compositor.acceptSuggestion(baseSuggestion, baseContext, postTransform);
|
||||
assert.equal(reversion.transformId, baseSuggestion.transformId);
|
||||
assert.equal(reversion.transformId, -baseSuggestion.transformId);
|
||||
assert.equal(reversion.id, -baseSuggestion.id);
|
||||
|
||||
let appliedContext = models.applyTransform(baseSuggestion.transform, baseContext);
|
||||
|
|
@ -802,7 +867,7 @@ describe('ModelCompositor', function() {
|
|||
|
||||
let baseSuggestion = initialSuggestions[1];
|
||||
let reversion = compositor.acceptSuggestion(baseSuggestion, baseContext, postTransform);
|
||||
assert.equal(reversion.transformId, baseSuggestion.transformId);
|
||||
assert.equal(reversion.transformId, -baseSuggestion.transformId);
|
||||
assert.equal(reversion.id, -baseSuggestion.id);
|
||||
|
||||
// Accepting the suggestion adds an extra context state.
|
||||
|
|
|
|||
|
|
@ -175,11 +175,23 @@ class ModelCompositor {
|
|||
finalInput = inputTransform; // A fallback measure. Greatly matters for empty contexts.
|
||||
}
|
||||
|
||||
let deleteLeft = 0;
|
||||
// remove actual token string. If new token, there should be nothing to delete.
|
||||
if(!newEmptyToken) {
|
||||
// If this is triggered from a backspace, make sure to use its results
|
||||
// and also include its left-deletions! It's the one post-input context case.
|
||||
if(allowBksp) {
|
||||
deleteLeft = this.wordbreak(postContext).kmwLength() + inputTransform.deleteLeft;
|
||||
} else {
|
||||
// Normal case - use the pre-input context.
|
||||
deleteLeft = this.wordbreak(context).kmwLength();
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the existing context with the correction.
|
||||
let correctionTransform: Transform = {
|
||||
insert: correction, // insert correction string
|
||||
// remove actual token string. If new token, there should be nothing to delete.
|
||||
deleteLeft: newEmptyToken ? 0 : this.wordbreak(context).length,
|
||||
deleteLeft: deleteLeft,
|
||||
id: inputTransform.id // The correction should always be based on the most recent external transform/transcription ID.
|
||||
}
|
||||
|
||||
|
|
@ -220,10 +232,15 @@ class ModelCompositor {
|
|||
}
|
||||
|
||||
// Section 2 - post-analysis for our generated predictions, managing 'keep'.
|
||||
|
||||
// Assumption: Duplicated 'displayAs' properties indicate duplicated Suggestions.
|
||||
// When true, we can use an 'associative array' to de-duplicate everything.
|
||||
let suggestionDistribMap: {[key: string]: ProbabilityMass<Suggestion>} = {};
|
||||
let currentCasing: CasingForm = null;
|
||||
if(lexicalModel.languageUsesCasing) {
|
||||
currentCasing = this.detectCurrentCasing(postContext);
|
||||
}
|
||||
|
||||
let baseWord = this.wordbreak(context);
|
||||
|
||||
// Deduplicator + annotator of 'keep' suggestions.
|
||||
for(let prediction of rawPredictions) {
|
||||
|
|
@ -253,6 +270,16 @@ class ModelCompositor {
|
|||
keepOption.p += prediction.p;
|
||||
}
|
||||
} else {
|
||||
// Apply capitalization rules now; facilitates de-duplication of suggestions
|
||||
// that may be caused as a result.
|
||||
//
|
||||
// Example: "apple" and "Apple" are separate when 'lower', but identical for 'initial' and 'upper'.
|
||||
if(currentCasing && currentCasing != 'lower') {
|
||||
this.applySuggestionCasing(prediction.sample, baseWord, currentCasing);
|
||||
// update the mapping string, too.
|
||||
displayText = prediction.sample.displayAs;
|
||||
}
|
||||
|
||||
let existingSuggestion = suggestionDistribMap[displayText];
|
||||
if(existingSuggestion) {
|
||||
existingSuggestion.p += prediction.p;
|
||||
|
|
@ -306,21 +333,28 @@ class ModelCompositor {
|
|||
|
||||
// Apply 'after word' punctuation and casing (when applicable). Also, set suggestion IDs.
|
||||
// We delay until now so that utility functions relying on the unmodified Transform may execute properly.
|
||||
let currentCasing: CasingForm = null;
|
||||
if(lexicalModel.languageUsesCasing) {
|
||||
currentCasing = this.detectCurrentCasing(postContext);
|
||||
}
|
||||
|
||||
let compositor = this;
|
||||
let baseWord = this.wordbreak(context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
if(currentCasing && currentCasing != 'lower') {
|
||||
compositor.applySuggestionCasing(suggestion, baseWord, currentCasing);
|
||||
}
|
||||
|
||||
// Valid 'keep' suggestions may have zero length; we still need to evaluate the following code
|
||||
// for such cases.
|
||||
suggestion.transform.insert += punctuation.insertAfterWord;
|
||||
|
||||
// Do we need to manipulate the suggestion's transform based on the current state of the context?
|
||||
if(!context.right) {
|
||||
// Only insert wordbreak characters if we're at the end of the context.
|
||||
suggestion.transform.insert += punctuation.insertAfterWord;
|
||||
} else {
|
||||
// If we're mid-word, delete its original post-caret text.
|
||||
const tokenization = compositor.tokenize(context);
|
||||
if(tokenization && tokenization.caretSplitsToken) {
|
||||
// While we wait on the ability to provide a more 'ideal' solution, let's at least
|
||||
// go with a more stable, if slightly less ideal, solution for now.
|
||||
//
|
||||
// A predictive text default (on iOS, at least) - immediately wordbreak
|
||||
// on suggestions accepted mid-word.
|
||||
suggestion.transform.insert += punctuation.insertAfterWord;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a suggestion after wordbreak input, make sure we preserve the wordbreak transform!
|
||||
if(prefixTransform) {
|
||||
|
|
@ -430,7 +464,6 @@ class ModelCompositor {
|
|||
// Step 1: generate and save the reversion's Transform.
|
||||
let sourceTransform = suggestion.transform;
|
||||
let deletedLeftChars = context.left.kmwSubstr(-sourceTransform.deleteLeft, sourceTransform.deleteLeft);
|
||||
// right deletion is currently not implemented.
|
||||
let insertedLength = sourceTransform.insert.kmwLength();
|
||||
|
||||
let reversionTransform: Transform = {
|
||||
|
|
@ -450,7 +483,15 @@ class ModelCompositor {
|
|||
postContext = models.applyTransform(postTransform, postContext);
|
||||
}
|
||||
|
||||
let revertedPrefix = this.wordbreak(postContext);
|
||||
let revertedPrefix: string;
|
||||
let postContextTokenization = this.tokenize(postContext);
|
||||
if(postContextTokenization) {
|
||||
// Handles display string for reversions triggered by accepting a suggestion mid-token.
|
||||
revertedPrefix = postContextTokenization.left[postContextTokenization.left.length-1];
|
||||
revertedPrefix += postContextTokenization.caretSplitsToken ? postContextTokenization.right[0] : '';
|
||||
} else {
|
||||
revertedPrefix = this.wordbreak(postContext);
|
||||
}
|
||||
|
||||
let firstConversion = models.transformToSuggestion(reversionTransform);
|
||||
firstConversion.displayAs = revertedPrefix;
|
||||
|
|
@ -460,7 +501,7 @@ class ModelCompositor {
|
|||
// set the Reversion's ID directly.
|
||||
let reversion = this.toAnnotatedSuggestion(firstConversion, 'revert');
|
||||
if(suggestion.transformId != null) {
|
||||
reversion.transformId = suggestion.transformId;
|
||||
reversion.transformId = -suggestion.transformId;
|
||||
}
|
||||
if(suggestion.id != null) {
|
||||
// Since a reversion inverts its source suggestion, we set its ID to be the
|
||||
|
|
@ -493,7 +534,13 @@ class ModelCompositor {
|
|||
let compositor = this;
|
||||
let fallbackSuggestions = function() {
|
||||
let revertedContext = models.applyTransform(reversion.transform, context);
|
||||
return compositor.predict({ insert: '', deleteLeft: 0}, revertedContext);
|
||||
let suggestions = compositor.predict({insert: '', deleteLeft: 0}, revertedContext);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// A reversion's transform ID is the additive inverse of its original suggestion;
|
||||
// we revert to the state of said original suggestion.
|
||||
suggestion.transformId = -reversion.transformId;
|
||||
});
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
if(!this.contextTracker) {
|
||||
|
|
@ -525,9 +572,16 @@ class ModelCompositor {
|
|||
// Will need to be modified a bit if/when phrase-level suggestions are implemented.
|
||||
// Those will be tracked on the first token of the phrase, which won't be the tail
|
||||
// if they cover multiple tokens.
|
||||
return this.contextTracker.newest.tail.replacements.map(function(trackedSuggestion) {
|
||||
let suggestions = this.contextTracker.newest.tail.replacements.map(function(trackedSuggestion) {
|
||||
return trackedSuggestion.suggestion;
|
||||
});
|
||||
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// A reversion's transform ID is the additive inverse of its original suggestion;
|
||||
// we revert to the state of said original suggestion.
|
||||
suggestion.transformId = -reversion.transformId;
|
||||
});
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private wordbreak(context: Context): string {
|
||||
|
|
@ -549,6 +603,16 @@ class ModelCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
private tokenize(context: Context): models.Tokenization {
|
||||
let model = this.lexicalModel;
|
||||
|
||||
if(model.wordbreaker) {
|
||||
return models.tokenize(model.wordbreaker, context);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public resetContext(context: Context) {
|
||||
// Force-resets the context, throwing out any previous fat-finger data, etc.
|
||||
// Designed for use when the caret has been directly moved and/or the context sourced from a different control
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ files:
|
|||
|
||||
- source: /ios/engine/KMEI/KeymanEngine/Classes/en.lproj/ResourceInfoView.strings
|
||||
dest: /ios/engine/ResourceInfoView.strings
|
||||
translation: /ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/%osx_code%/%original_file_name%
|
||||
translation: /ios/engine/KMEI/KeymanEngine/Classes/%osx_code%/%original_file_name%
|
||||
|
||||
- source: /ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings
|
||||
dest: /ios/engine/Localizable.strings
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ display_usage ( ) {
|
|||
echo "Usage: $0 [-test] [-publish-to-npm]"
|
||||
echo " $0 -help"
|
||||
echo
|
||||
echo " -help displays this screen and exits"
|
||||
echo " -test runs unit tests after building"
|
||||
echo " -tdd runs unit tests WITHOUT building"
|
||||
echo " -publish-to-npm publishes the current version to the npm package index"
|
||||
echo " -dry-run do build, etc, but don't actually publish"
|
||||
echo " -help displays this screen and exits"
|
||||
echo " -test runs unit tests after building"
|
||||
echo " -tdd runs unit tests WITHOUT building"
|
||||
echo " -skip-package-install, -S skip package installation"
|
||||
echo " -publish-to-npm publishes the current version to the npm package index"
|
||||
echo " -dry-run do build, etc, but don't actually publish"
|
||||
}
|
||||
|
||||
################################ Main script ################################
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ function _parseWordList(wordlist: WordList, source: WordListSource): void {
|
|||
}
|
||||
wordsSeenInThisFile.add(wordform);
|
||||
|
||||
wordlist[wordform] = (wordlist[wordform] || 0) + count;
|
||||
wordlist[wordform] = (isNaN(wordlist[wordform]) ? 0 : wordlist[wordform] || 0) + count;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,15 +459,22 @@ namespace Trie {
|
|||
* @param node The node to start summing weights.
|
||||
*/
|
||||
function sumWeights(node: Node): number {
|
||||
let val: number;
|
||||
if (node.type === 'leaf') {
|
||||
return node.entries
|
||||
val = node.entries
|
||||
.map(entry => entry.weight)
|
||||
//.map(entry => isNaN(entry.weight) ? 1 : entry.weight)
|
||||
.reduce((acc, count) => acc + count, 0);
|
||||
} else {
|
||||
return Object.keys(node.children)
|
||||
val = Object.keys(node.children)
|
||||
.map((key) => sumWeights(node.children[key]))
|
||||
.reduce((acc, count) => acc + count, 0);
|
||||
}
|
||||
|
||||
if(isNaN(val)) {
|
||||
console.error("Unexpected NaN has appeared!");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ export default class KmpCompiler {
|
|||
// 1. Unwrap arrays (and convert to array where single object)
|
||||
// 2. Fix casing on `iD`
|
||||
// 3. Rewrap info, keyboard.languages, lexicalModel.languages, startMenu.items elements
|
||||
// 4. Convert options.followKeyboardVersion to a bool
|
||||
// 5. Filenames need to be basenames (but this comes after processing)
|
||||
// 4. Remove options.followKeyboardVersion, file.fileType
|
||||
// 5. Convert file.copyLocation to a Number
|
||||
// 6. Filenames need to be basenames (but this comes after processing)
|
||||
//
|
||||
|
||||
// Helper functions
|
||||
|
|
@ -66,9 +67,7 @@ export default class KmpCompiler {
|
|||
|
||||
let kmp: KmpJsonFile = {
|
||||
system: kps.system,
|
||||
options: {
|
||||
followKeyboardVersion: kps.options.followKeyboardVersion === ''
|
||||
}
|
||||
options: {}
|
||||
};
|
||||
|
||||
// Fill in additional fields
|
||||
|
|
@ -76,10 +75,7 @@ export default class KmpCompiler {
|
|||
let keys: (keyof KpsFileOptions & keyof KmpJsonFileOptions)[] = ['executeProgram', 'graphicFile', 'msiFilename', 'msiOptions', 'readmeFile'];
|
||||
for (let element of keys) {
|
||||
if (kps.options[element]) {
|
||||
// TypeScript thinks it's possible to assign to followKeyboardVersion (a
|
||||
// boolean), but in reality, all of the other keys are strings.
|
||||
// Politely inform TypeScript that it's okay to do this assignment:
|
||||
(<string> kmp.options[element]) = kps.options[element];
|
||||
kmp.options[element] = kps.options[element];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +84,14 @@ export default class KmpCompiler {
|
|||
}
|
||||
|
||||
if(kps.files && kps.files.file) {
|
||||
kmp.files = arrayWrap(kps.files.file);
|
||||
kmp.files = arrayWrap(kps.files.file).map((file: KpsFileContentFile) => {
|
||||
return {
|
||||
name: file.name,
|
||||
description: file.description,
|
||||
copyLocation: parseInt(file.copyLocation, 10)
|
||||
// note: we don't emit fileType as that is not permitted in kmp.json
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(kps.keyboards && kps.keyboards.keyboard) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ interface KmpJsonFileSystem {
|
|||
}
|
||||
|
||||
interface KmpJsonFileOptions {
|
||||
followKeyboardVersion: boolean;
|
||||
readmeFile?: string;
|
||||
graphicFile?: string;
|
||||
executeProgram?: string;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
"keymanDeveloperVersion": "12.0.1500.0",
|
||||
"fileVersion": "12.0"
|
||||
},
|
||||
"options": {
|
||||
"followKeyboardVersion": true
|
||||
},
|
||||
"options": {},
|
||||
"info": {
|
||||
"author": {
|
||||
"description": "Eddie Antonio Santos",
|
||||
|
|
@ -25,8 +23,7 @@
|
|||
{
|
||||
"name": "..\\build\\example.qaa.sencoten.model.js",
|
||||
"description": "Lexical model example.qaa.sencoten.model.js",
|
||||
"copyLocation": "0",
|
||||
"fileType": ".model.js"
|
||||
"copyLocation": 0
|
||||
}
|
||||
],
|
||||
"lexicalModels": [
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@
|
|||
CE9E95CC24CE786900F6DD78 /* UniversalLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalLinks.swift; sourceTree = "<group>"; };
|
||||
CE9E95CE24CE7A2C00F6DD78 /* UniversalLinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalLinkTests.swift; sourceTree = "<group>"; };
|
||||
CE9ECB1D24D3A8CC007C5718 /* PackageInstallView_iPad.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PackageInstallView_iPad.xib; sourceTree = "<group>"; };
|
||||
CEA1486E2407808F00C6ECD2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEA1486E2407808F00C6ECD2 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEA14871240780E100C6ECD2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ResourceInfoView.xib; sourceTree = "<group>"; };
|
||||
CEA14874240780EF00C6ECD2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
CEA148772407869200C6ECD2 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
|
|
@ -520,6 +520,14 @@
|
|||
CEA9670A24BEC8030035AACF /* EngineStateBundler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EngineStateBundler.swift; sourceTree = "<group>"; };
|
||||
CEA9670C24BEEFF80035AACF /* khmer_angkor update-base.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "khmer_angkor update-base.bundle"; sourceTree = "<group>"; };
|
||||
CEA9670E24BEF05A0035AACF /* Updates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updates.swift; sourceTree = "<group>"; };
|
||||
CEACC90125F07C81006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
CEACC90325F07CAF006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEACC90425F07CB6006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEACC90525F07CBA006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
CEACC90625F07CBC006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = km; path = km.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
CEACC90725F07CDA006EAB45 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
CEACC90825F07CE9006EAB45 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEACC90925F07CED006EAB45 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
CEB8276624C6811800F3D39C /* KeymanHostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanHostTests.swift; sourceTree = "<group>"; };
|
||||
CEC0C66B2410AC9A003E1BCD /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = ../../Carthage/Build/iOS/Sentry.framework; sourceTree = "<group>"; };
|
||||
CEC0C6762410E049003E1BCD /* SentryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryManager.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1293,6 +1301,8 @@
|
|||
en,
|
||||
Base,
|
||||
km,
|
||||
de,
|
||||
fr,
|
||||
);
|
||||
mainGroup = F243887314BBD43000A3E055;
|
||||
productRefGroup = F243887F14BBD43000A3E055 /* Products */;
|
||||
|
|
@ -1672,6 +1682,9 @@
|
|||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CE96E42C24D1229A005B8E5A /* en */,
|
||||
CEACC90525F07CBA006EAB45 /* de */,
|
||||
CEACC90625F07CBC006EAB45 /* km */,
|
||||
CEACC90925F07CED006EAB45 /* fr */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1680,6 +1693,9 @@
|
|||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CEA1486E2407808F00C6ECD2 /* en */,
|
||||
CEACC90325F07CAF006EAB45 /* de */,
|
||||
CEACC90425F07CB6006EAB45 /* km */,
|
||||
CEACC90825F07CE9006EAB45 /* fr */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1690,6 +1706,8 @@
|
|||
CEA14871240780E100C6ECD2 /* Base */,
|
||||
CEA14874240780EF00C6ECD2 /* en */,
|
||||
CEA148772407869200C6ECD2 /* km */,
|
||||
CEACC90125F07C81006EAB45 /* de */,
|
||||
CEACC90725F07CDA006EAB45 /* fr */,
|
||||
);
|
||||
name = ResourceInfoView.xib;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ protocol HTTPDownloadDelegate: class {
|
|||
class HTTPDownloader: NSObject {
|
||||
var queue: [HTTPDownloadRequest] = []
|
||||
// TODO: Make unowned
|
||||
weak var handler: HTTPDownloadDelegate?
|
||||
/*weak*/ var handler: HTTPDownloadDelegate? // 'weak' interferes with installs
|
||||
// from the app's browser.
|
||||
var currentRequest: HTTPDownloadRequest?
|
||||
var downloadSession: URLSession!
|
||||
public var userInfo: [String: Any] = [:]
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
if(self.swallowBackspaceTextChange) {
|
||||
// A single keyboard processing command should never trigger two of these in a row;
|
||||
// only one output function will perform deletions.
|
||||
|
||||
|
||||
// This should allow us to debug any failures of this assumption.
|
||||
// So far, only occurs when debugging a breakpoint during a touch event on BKSP,
|
||||
// so all seems good.
|
||||
|
|
|
|||
|
|
@ -366,7 +366,9 @@ extension KeymanWebViewController {
|
|||
* thereafter for settings updates.
|
||||
*/
|
||||
func setSentryState(enabled: Bool = SentryManager.enabled) {
|
||||
webView?.evaluateJavaScript("sentryManager.enabled = \(enabled ? "true" : "false")")
|
||||
// This may be called before the page - and thus the `sentryManager` variable -
|
||||
// is ready. It's a known limitation, so why log error reports for it?
|
||||
webView?.evaluateJavaScript("try { sentryManager.enabled = \(enabled ? "true" : "false") } catch(err) { }")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */
|
||||
"z2O-MT-IoV.text" = "ស្កេនកូដនេះដើម្បីផ្ទុកក្ដារចុចនេះនៅលើឧបករណ៍ផ្សេង";
|
||||
|
|
@ -8,4 +8,27 @@
|
|||
|
||||
import XCGLogger
|
||||
|
||||
public let log = XCGLogger(identifier: "KeymanEngine", includeDefaultDestinations: true)
|
||||
// From XCGLogger docs:
|
||||
// Note: This creates the log object lazily, which means it's not created until it's actually needed.
|
||||
public let log: XCGLogger = {
|
||||
// Default: the 'console', which is read by Xcode but doesn't reach the system logs.
|
||||
let mainLog = XCGLogger(identifier: "KeymanEngine", includeDefaultDestinations: false)
|
||||
|
||||
// Ensures our log messages go out to the device's system log as well as the console.
|
||||
let systemLogDest = AppleSystemLogDestination(identifier: "")
|
||||
systemLogDest.showLogIdentifier = true
|
||||
|
||||
mainLog.add(destination: systemLogDest)
|
||||
|
||||
// Temporary logging level to ensure that app details are reported properly.
|
||||
mainLog.outputLevel = .info
|
||||
mainLog.logAppDetails()
|
||||
|
||||
#if DEBUG
|
||||
mainLog.outputLevel = .debug
|
||||
#else
|
||||
mainLog.outputLevel = .warning
|
||||
#endif
|
||||
|
||||
return mainLog
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -121,7 +121,9 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
var associationQueryProgress: [Int: LanguagePickAssociator.Progress] = [:]
|
||||
var installProgressMap: [KeymanPackage.Key: PackageInstallResult?] = [:]
|
||||
|
||||
let progressCallback: ProgressReceiver
|
||||
let externalProgressCallback: ProgressReceiver
|
||||
var promptProgressCallback: ProgressReceiver?
|
||||
|
||||
let downloadManager: ResourceDownloadManager
|
||||
|
||||
var isCancelled = false
|
||||
|
|
@ -129,7 +131,7 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
init(with associationSpecs: [Associator], downloadManager: ResourceDownloadManager, receiver: @escaping ProgressReceiver) {
|
||||
self.associationSpecs = associationSpecs
|
||||
self.downloadManager = downloadManager
|
||||
self.progressCallback = receiver
|
||||
self.externalProgressCallback = receiver
|
||||
}
|
||||
|
||||
// Since progress info is stored here, it makes the most sense to track progress-related
|
||||
|
|
@ -139,12 +141,19 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
return !isCancelled && pickingCompleted
|
||||
}
|
||||
|
||||
internal func notifyProgress(_ status: Progress) {
|
||||
// Prompt gets first dibs - that way, if a prompt exists, its UI code
|
||||
// executes before control transfers back to other modules.
|
||||
self.promptProgressCallback?(status)
|
||||
self.externalProgressCallback(status)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial level of progress made toward the overall installation at the time that language selections were
|
||||
* finalized.
|
||||
*/
|
||||
internal func initializeProgress() {
|
||||
self.progressCallback(.starting)
|
||||
notifyProgress(.starting)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -153,7 +162,7 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
internal func reportProgress(complete: Bool = false) {
|
||||
if reportsProgress {
|
||||
if complete {
|
||||
progressCallback(.complete)
|
||||
notifyProgress(.complete)
|
||||
} else {
|
||||
//progressCallback(.inProgress)
|
||||
}
|
||||
|
|
@ -161,7 +170,7 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
}
|
||||
|
||||
internal func reportCancelled() {
|
||||
progressCallback(.cancelled)
|
||||
notifyProgress(.cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -388,8 +397,7 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
* May only be called once during the lifetime of its instance and is mutually exclusive with `pickLanguages`,
|
||||
* the programmatic alternative.
|
||||
*/
|
||||
public func promptForLanguages(inNavigationVC navVC: UINavigationController,
|
||||
uiCompletionHandler: @escaping (() -> Void)) {
|
||||
public func promptForLanguages(inNavigationVC navVC: UINavigationController) {
|
||||
guard self.associationQueriers == nil, !closureShared.pickingCompleted else {
|
||||
fatalError("Invalid state - language picking has already been triggered.")
|
||||
}
|
||||
|
|
@ -397,12 +405,21 @@ public class AssociatingPackageInstaller<Resource: LanguageResource, Package: Ty
|
|||
initializeSynchronizationGroups()
|
||||
constructAssociationPickers()
|
||||
|
||||
closureShared.installGroup.enter()
|
||||
|
||||
let wrappedCompletionHandler = {
|
||||
self.closureShared.installGroup.leave()
|
||||
}
|
||||
|
||||
let pickerPrompt = PackageInstallViewController<Resource>(for: self.package,
|
||||
defaultLanguageCode: defaultLgCode,
|
||||
languageAssociators: associationQueriers!,
|
||||
pickingCompletionHandler: coreInstallationClosure(),
|
||||
uiCompletionHandler: uiCompletionHandler)
|
||||
uiCompletionHandler: wrappedCompletionHandler)
|
||||
|
||||
closureShared.promptProgressCallback = { progress in
|
||||
pickerPrompt.progressUpdate(progress)
|
||||
}
|
||||
navVC.pushViewController(pickerPrompt, animated: true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
private let languageCode: String?
|
||||
private let session: URLSession
|
||||
|
||||
private var progressView: UIProgressView?
|
||||
private var observation: NSKeyValueObservation? = nil
|
||||
|
||||
private static var ENDPOINT_ROOT: URL {
|
||||
var baseURL = KeymanHosts.KEYMAN_COM
|
||||
baseURL.appendPathComponent("go")
|
||||
|
|
@ -93,6 +96,10 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
observation = nil
|
||||
}
|
||||
|
||||
public override func loadView() {
|
||||
let webView = WKWebView()
|
||||
webView.navigationDelegate = self
|
||||
|
|
@ -104,6 +111,18 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
webView.load(URLRequest(url: KeyboardSearchViewController.ENDPOINT_ROOT))
|
||||
}
|
||||
|
||||
progressView = UIProgressView(progressViewStyle: .bar)
|
||||
progressView!.translatesAutoresizingMaskIntoConstraints = false
|
||||
observation = webView.observe(\.estimatedProgress) { _, _ in
|
||||
if let progressView = self.progressView {
|
||||
progressView.setProgress(Float(webView.estimatedProgress), animated: true)
|
||||
progressView.isHidden = progressView.progress > 0.99
|
||||
}
|
||||
}
|
||||
progressView!.setProgress(1, animated: false)
|
||||
progressView!.isHidden = true
|
||||
|
||||
webView.addSubview(progressView!)
|
||||
view = webView
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +150,25 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
}
|
||||
|
||||
decisionHandler(.allow)
|
||||
// Makes it clear that there IS a progress bar, in case of super-slow response.
|
||||
progressView?.setProgress(0, animated: false)
|
||||
// This way, if the load is instant, the 0.01 doesn't really stand out.
|
||||
progressView?.setProgress(0.01, animated: true)
|
||||
progressView?.isHidden = false
|
||||
}
|
||||
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let navVC = self.navigationController {
|
||||
progressView?.topAnchor.constraint(equalTo: navVC.navigationBar.bottomAnchor).isActive = true
|
||||
} else {
|
||||
progressView?.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
|
||||
}
|
||||
|
||||
progressView?.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
|
||||
progressView?.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
private var navMapping: [NavigationMode : UIBarButtonItem] = [:]
|
||||
|
||||
private var dismissalBlock: (() -> Void)? = nil
|
||||
private weak var welcomeView: UIView?
|
||||
private var mayPick: Bool = true
|
||||
|
||||
public init(for package: Resource.Package,
|
||||
defaultLanguageCode: String? = nil,
|
||||
|
|
@ -267,9 +269,11 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
}
|
||||
|
||||
set(mode) {
|
||||
leftNavMode = mode
|
||||
if mayPick {
|
||||
leftNavMode = mode
|
||||
|
||||
navigationItem.leftBarButtonItem = navMapping[mode]
|
||||
navigationItem.leftBarButtonItem = navMapping[mode]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,9 +283,11 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
}
|
||||
|
||||
set(mode) {
|
||||
rightNavMode = mode
|
||||
if mayPick {
|
||||
rightNavMode = mode
|
||||
|
||||
navigationItem.rightBarButtonItem = navMapping[mode]
|
||||
navigationItem.rightBarButtonItem = navMapping[mode]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -349,20 +355,29 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
Manager.shared.shouldReloadKeyboard = true
|
||||
self.pickingCompletionHandler(selectedResources.map { $0.typedFullID })
|
||||
|
||||
let dismissalBlock = {
|
||||
if let nvc = self.navigationController {
|
||||
self.dismiss(animated: true)
|
||||
nvc.popToRootViewController(animated: false)
|
||||
} else { // Otherwise, if the root view of a navigation controller, dismiss it outright. (pop not available)
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
// Prevent swipe dismissal.
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
self.isModalInPresentation = true
|
||||
}
|
||||
|
||||
// No more selection-manipulation allowed.
|
||||
// This matters when there's no welcome page available.
|
||||
languageTable.isUserInteractionEnabled = false
|
||||
// Prevent extra 'install' commands and nav-bar related manipulation.
|
||||
self.navigationItem.leftBarButtonItem?.isEnabled = false
|
||||
self.rightNavigationMode = .none
|
||||
self.mayPick = false
|
||||
|
||||
let dismissalBlock = {
|
||||
self.associators.forEach { $0.pickerFinalized() }
|
||||
}
|
||||
|
||||
// First, show the package's welcome - if it exists.
|
||||
if let welcomeVC = PackageWebViewController(for: package, page: .welcome) {
|
||||
self.dismissalBlock = dismissalBlock
|
||||
// Prevent swipe dismissal.
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
welcomeVC.isModalInPresentation = true
|
||||
}
|
||||
|
||||
let subNavVC = UINavigationController(rootViewController: welcomeVC)
|
||||
_ = subNavVC.view
|
||||
|
|
@ -383,8 +398,16 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
subNavVC.presentationController?.delegate = self
|
||||
|
||||
self.present(subNavVC, animated: true, completion: nil)
|
||||
self.welcomeView = welcomeVC.view
|
||||
|
||||
self.dismissalBlock = {
|
||||
// Tells the user that we've received the 'done' command.
|
||||
doneItem.isEnabled = false
|
||||
dismissalBlock()
|
||||
}
|
||||
} else {
|
||||
dismissalBlock()
|
||||
self.dismissalBlock = dismissalBlock
|
||||
onWelcomeDismissed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -393,10 +416,33 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
}
|
||||
|
||||
@objc private func onWelcomeDismissed() {
|
||||
self.dismissalBlock?()
|
||||
self.dismissalBlock = nil
|
||||
if let dismissalBlock = self.dismissalBlock {
|
||||
dismissalBlock()
|
||||
self.dismissalBlock = nil
|
||||
|
||||
self.uiCompletionHandler()
|
||||
// Tell our owner (the AssociatingPackageInstaller) that all UI interactions are done.
|
||||
// Triggers synchronization code, so make sure it only runs once!
|
||||
self.uiCompletionHandler()
|
||||
}
|
||||
|
||||
// Show a spinner forever.
|
||||
// When installation is complete, this controller's view will be dismissed,
|
||||
// removing said spinner.
|
||||
let activitySpinner = Alerts.constructActivitySpinner()
|
||||
|
||||
// Determine the top-most view. If we're presenting the welcome page, THAT.
|
||||
// If we aren't, our directly-owned view should be the top-most.
|
||||
let activeView: UIView = self.welcomeView ?? view
|
||||
|
||||
activitySpinner.center = activeView.center
|
||||
activitySpinner.startAnimating()
|
||||
activeView.addSubview(activitySpinner)
|
||||
|
||||
activitySpinner.centerXAnchor.constraint(equalTo: activeView.centerXAnchor).isActive = true
|
||||
activitySpinner.centerYAnchor.constraint(equalTo: activeView.centerYAnchor).isActive = true
|
||||
|
||||
// Note: we do NOT block user interaction; we merely bind them to the active view.
|
||||
// Why prevent them from reading more on the welcome page while they wait?
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, titleForHeaderInSection: Int) -> String? {
|
||||
|
|
@ -515,4 +561,20 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
|
||||
associators.forEach { $0.deselectLanguages( Set([languages[indexPath.row].id]) ) }
|
||||
}
|
||||
|
||||
internal func progressUpdate<Package: TypedKeymanPackage<Resource>>(_ status: AssociatingPackageInstaller<Resource, Package>.Progress) where Resource.Package == Package {
|
||||
switch(status) {
|
||||
case .starting, .inProgress:
|
||||
// nothing worth note
|
||||
break
|
||||
// All UI interactions have been completed AND installation is fully complete.
|
||||
case .complete, .cancelled:
|
||||
if let nvc = self.navigationController {
|
||||
self.dismiss(animated: true)
|
||||
nvc.popToRootViewController(animated: false)
|
||||
} else { // Otherwise, if the root view of a navigation controller, dismiss it outright. (pop not available)
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -682,4 +682,66 @@ public class ResourceDownloadManager {
|
|||
value: PackageDownloadFailedNotification(packageKey: packageKey, error: error))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Designed for downloading KMP files when no metadata is available in advance.
|
||||
*/
|
||||
public func downloadRawKMP(from url: URL, handler: @escaping (URL?, Error?) -> Void) {
|
||||
|
||||
// First, we need something to handle the download.
|
||||
class NoMetadataDelegate: HTTPDownloadDelegate {
|
||||
private let closure: (URL?, Error?) -> Void
|
||||
|
||||
init(withHandler handler: @escaping (URL?, Error?) -> Void) {
|
||||
self.closure = handler;
|
||||
}
|
||||
|
||||
func downloadRequestStarted(_ request: HTTPDownloadRequest) {
|
||||
// Not relevant
|
||||
}
|
||||
|
||||
func downloadRequestFinished(_ request: HTTPDownloadRequest) {
|
||||
if request.responseStatusCode != 200 {
|
||||
// Possible request error (400 Bad Request, 404 Not Found, etc.)
|
||||
let error = DownloadError.failed(.responseCode(request.responseStatusCode ?? 400,
|
||||
request.responseStatusMessage ?? "",
|
||||
request.url))
|
||||
|
||||
// Now that we've synthesized an appropriate error instance, use the same handler
|
||||
// as for HTTPDownloader's 'failed' condition.
|
||||
downloadRequestFailed(request, with: error)
|
||||
} else {
|
||||
self.closure(URL(fileURLWithPath: request.destinationFile!), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRequestFailed(_ request: HTTPDownloadRequest, with error: Error?) {
|
||||
self.closure(nil, error)
|
||||
}
|
||||
|
||||
func downloadQueueFinished(_ queue: HTTPDownloader) {
|
||||
// Not relevant
|
||||
}
|
||||
|
||||
func downloadQueueCancelled(_ queue: HTTPDownloader) {
|
||||
// Not relevant, but to be safe...
|
||||
self.closure(nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
let delegate = NoMetadataDelegate(withHandler: handler)
|
||||
let downloader = HTTPDownloader(delegate, session: self.session)
|
||||
let request = HTTPDownloadRequest(url: url, downloadType: .downloadFile)
|
||||
|
||||
// Since we don't know the package key in advance, we'll download it to
|
||||
// the app's cache directory, then figure everything out once we open it.
|
||||
let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let tempFilename = url.lastPathComponent
|
||||
let cachedDest = cachesDir.appendingPathComponent(tempFilename)
|
||||
|
||||
request.destinationFile = cachedDest.path
|
||||
|
||||
downloader.addRequest(request)
|
||||
downloader.run()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,27 +228,23 @@ public class ResourceFileManager {
|
|||
activitySpinner.centerXAnchor.constraint(equalTo: rootVC.view.centerXAnchor).isActive = true
|
||||
activitySpinner.centerYAnchor.constraint(equalTo: rootVC.view.centerYAnchor).isActive = true
|
||||
rootVC.view.isUserInteractionEnabled = false
|
||||
} else if status == .complete {
|
||||
} else if status == .complete || status == .cancelled {
|
||||
// Report completion!
|
||||
activitySpinner.stopAnimating()
|
||||
activitySpinner.removeFromSuperview()
|
||||
rootVC.view.isUserInteractionEnabled = true
|
||||
rootVC.dismiss(animated: true, completion: nil)
|
||||
rootVC.dismiss(animated: true) {
|
||||
Manager.shared.showKeyboard()
|
||||
}
|
||||
successHandler?(package)
|
||||
}
|
||||
}
|
||||
|
||||
if let navVC = rootVC as? UINavigationController {
|
||||
packageInstaller.promptForLanguages(inNavigationVC: navVC) {
|
||||
// The user will be on the main screen after this, so we should resummon the keyboard.
|
||||
Manager.shared.showKeyboard()
|
||||
}
|
||||
packageInstaller.promptForLanguages(inNavigationVC: navVC)
|
||||
} else {
|
||||
let nvc = UINavigationController.init()
|
||||
packageInstaller.promptForLanguages(inNavigationVC: nvc) {
|
||||
// The user will be on the main screen after this, so we should resummon the keyboard.
|
||||
Manager.shared.showKeyboard()
|
||||
}
|
||||
packageInstaller.promptForLanguages(inNavigationVC: nvc)
|
||||
rootVC.present(nvc, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -265,13 +261,14 @@ public class ResourceFileManager {
|
|||
}
|
||||
|
||||
public func buildKMPError(_ error: KMPError) -> UIAlertController {
|
||||
return buildSimpleAlert(title: "Error", message: error.localizedDescription)
|
||||
return buildSimpleAlert(title: NSLocalizedString("alert-error-title", bundle: engineBundle, comment: ""),
|
||||
message: error.localizedDescription)
|
||||
}
|
||||
|
||||
public func buildSimpleAlert(title: String, message: String, completionHandler: (() -> Void)? = nil ) -> UIAlertController {
|
||||
let alertController = UIAlertController(title: title, message: message,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("command-ok", bundle: engineBundle, comment: ""),
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: { _ in
|
||||
completionHandler?()
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ class LanguageSettingsViewController: UITableViewController {
|
|||
}
|
||||
|
||||
default:
|
||||
cell.textLabel?.text = "error"
|
||||
cell.textLabel?.text = NSLocalizedString("alert-error-title", bundle: engineBundle, comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
|
||||
/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */
|
||||
"z2O-MT-IoV.text" = "Scan this code to load this keyboard on another device";
|
||||
"z2O-MT-IoV.text" = "ស្កេនកូដនេះដើម្បីផ្ទុកក្ដារចុចនេះនៅលើឧបករណ៍ផ្សេង";
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -15,8 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
KeymanEngine.log.outputLevel = .debug
|
||||
KeymanEngine.log.logAppDetails()
|
||||
Manager.applicationGroupIdentifier = "group.KMEI"
|
||||
Manager.shared.canRemoveDefaultKeyboard = true
|
||||
|
||||
|
|
|
|||
|
|
@ -227,10 +227,11 @@ class MainViewController: UIViewController, UIAlertViewDelegate, TextViewDelegat
|
|||
}
|
||||
|
||||
@objc func showAlert(_ message: String) {
|
||||
let alertController = UIAlertController(title: "Keyboard Download Error",
|
||||
let engineBundle = Bundle(for: Manager.self)
|
||||
let alertController = UIAlertController(title: NSLocalizedString("alert-download-error-title", bundle: engineBundle, comment: ""),
|
||||
message: message,
|
||||
preferredStyle: UIAlertControllerStyle.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("command-ok", bundle: engineBundle, comment: ""),
|
||||
style: UIAlertActionStyle.default,
|
||||
handler: nil))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import KeymanEngine
|
|||
|
||||
class KeyboardViewController: InputViewController {
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
KeymanEngine.log.outputLevel = .debug
|
||||
KeymanEngine.log.logAppDetails()
|
||||
_ = log // forces init of the log, which is useful in sys-kbd contexts.
|
||||
Manager.applicationGroupIdentifier = "group.KMEI"
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ title: Basic Help
|
|||
|
||||
* [Switching Between Keyboards](switching-between-keyboards)
|
||||
|
||||
* [Installing Custom Keyboards](installing-custom-keyboards)
|
||||
* [Installing Custom Keyboards/Dictionaries](installing-custom-keyboards-dictionaries)
|
||||
* [Sharing Keyboards](sharing-keyboards)
|
||||
|
||||
* [Using the Keyman Browser](using-keyman-browser)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: Installing Custom Keyboards - Keyman for iPhone and iPad Help
|
||||
title: Installing Custom Keyboards/Dictionaries - Keyman for iPhone and iPad Help
|
||||
---
|
||||
|
||||
The following steps can also be used to install a dictionary package.
|
||||
The following steps can be used to install either a keyboard package or a dictionary package.
|
||||
|
||||
###Download the File
|
||||
### Download the File
|
||||
If downloading a custom keyboard from the internet, click the link to your custom keyboard package file.
|
||||
|
||||
For this example, we'll install a custom keyboard from a link in Safari. Our example keyboard is for the GFF Amharic 7 keyboard.
|
||||
|
|
@ -45,4 +45,4 @@ Selecting the notification seen at the top will lead you to the following page:
|
|||
|
||||
Select the big **Install on iPhone** (or
|
||||
**Install on iPad**) option will then download
|
||||
the file for easy installation. (Refer to [Installing custom keyboards](installing-custom-keyboards) as necessary.)
|
||||
the file for easy installation. (Refer to [Installing custom keyboards/dictionaries](installing-custom-keyboards-dictionaries) as necessary.)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ title: Keyman for iPhone and iPad 14.0 Help
|
|||
|
||||
### [Using Keyman for iPhone and iPad](basic/)
|
||||
* [Switching Between Keyboards](basic/switching-between-keyboards)
|
||||
* [Installing Custom Keyboards](basic/installing-custom-keyboards)
|
||||
* [Installing Custom Keyboards/Dictionaries](basic/installing-custom-keyboards-dictionaries)
|
||||
* [Sharing Keyboards](basic/sharing-keyboards)
|
||||
* [Using the Keyman Browser](basic/using-keyman-browser)
|
||||
* [Removing Keyboards](basic/uninstalling-keyboards)
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@
|
|||
CE002CB42408B4A5002026CE /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = ../../Carthage/Build/iOS/Sentry.framework; sourceTree = "<group>"; };
|
||||
CE1F5ECD23331DA400141F3E /* OfflineHelp.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = OfflineHelp.bundle; sourceTree = "<group>"; };
|
||||
CE2B1E4B21B6112B007D092E /* DeviceKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DeviceKit.framework; path = ../../Carthage/Build/iOS/DeviceKit.framework; sourceTree = "<group>"; };
|
||||
CE4C654524D0020900070856 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CE4C654524D0020900070856 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CE6137FF1FB99538009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE6138031FB999C8009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE79CDB42370111200010C06 /* Themes+Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Themes+Colors.xcassets"; sourceTree = "<group>"; };
|
||||
|
|
@ -325,6 +325,9 @@
|
|||
CE7C1AE1236925D800100C2C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
CE80AD32257F2B4A008D2150 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CEACC90E25F07D58006EAB45 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEACC90F25F07D5A006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEACC91225F07D77006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEF4E55523E95B7B0065B9C7 /* ImageBanner.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImageBanner.xib; sourceTree = "<group>"; };
|
||||
CEF4E55823E967140065B9C7 /* ImageBannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBannerViewController.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
|
@ -772,6 +775,8 @@
|
|||
en,
|
||||
Base,
|
||||
km,
|
||||
fr,
|
||||
de,
|
||||
);
|
||||
mainGroup = 98ABADA7176935E400B62590;
|
||||
productRefGroup = 98ABADB1176935E400B62590 /* Products */;
|
||||
|
|
@ -1090,6 +1095,9 @@
|
|||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CE4C654524D0020900070856 /* en */,
|
||||
CEACC90E25F07D58006EAB45 /* fr */,
|
||||
CEACC90F25F07D5A006EAB45 /* km */,
|
||||
CEACC91225F07D77006EAB45 /* de */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
vc.dismiss(animated: true, completion: nil)
|
||||
|
||||
if let package = rfm.prepareKMPInstall(from: destinationUrl, alertHost: vc) {
|
||||
// We choose to prompt the user for comfirmation, rather
|
||||
// First, explicitly hide the keyboard. Otherwise, the app may try to
|
||||
// redisplay it before package installation is fully complete.
|
||||
Manager.shared.hideKeyboard()
|
||||
|
||||
// We choose to prompt the user for confirmation, rather
|
||||
// than automatically installing the package.
|
||||
//
|
||||
// Since we're operating at the root, we want to present in a separate UINavigationController.
|
||||
|
|
@ -54,20 +58,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
SentryManager.start()
|
||||
// Forces the logs to initialize, as their definitions result in lazy init.
|
||||
// These references have been configured to also log app details.
|
||||
_ = log
|
||||
_ = KeymanEngine.log
|
||||
|
||||
UniversalLinks.externalLinkLauncher = { url in
|
||||
UIApplication.shared.openURL(url)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
KeymanEngine.log.outputLevel = .debug
|
||||
log.outputLevel = .debug
|
||||
KeymanEngine.log.logAppDetails()
|
||||
#else
|
||||
KeymanEngine.log.outputLevel = .warning
|
||||
log.outputLevel = .warning
|
||||
#endif
|
||||
|
||||
Manager.applicationGroupIdentifier = "group.KM4I"
|
||||
|
||||
// TODO: Assign a subclassed version of InputViewController that implements the image stuff.
|
||||
|
|
|
|||
|
|
@ -204,6 +204,38 @@ class WebBrowserViewController: UIViewController, UIWebViewDelegate, UIAlertView
|
|||
func webView(_ webView: UIWebView,
|
||||
shouldStartLoadWith request: URLRequest,
|
||||
navigationType: UIWebView.NavigationType) -> Bool {
|
||||
if request.url?.lastPathComponent.hasSuffix(".kmp") ?? false {
|
||||
// Can't have the browser auto-download with no way to select a different page.
|
||||
// Can't just ignore it, either, as the .kmp may result from a redirect from
|
||||
// the previous URL. (Like if using the keyman.com keyboard search!)
|
||||
let userData = UserDefaults.standard
|
||||
userData.set(nil as String?, forKey: webBrowserLastURLKey)
|
||||
userData.synchronize()
|
||||
|
||||
// The user is trying to download a .kmp, but the standard
|
||||
// UIWebView can't handle it properly.
|
||||
|
||||
ResourceDownloadManager.shared.downloadRawKMP(from: request.url!) { file, error in
|
||||
// do something!
|
||||
if let error = error {
|
||||
let alertTitle = NSLocalizedString("alert-error-title", bundle: Bundle(for: Manager.self), comment: "")
|
||||
let alert = ResourceFileManager.shared.buildSimpleAlert(title: alertTitle,
|
||||
message: error.localizedDescription)
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Re-use the standard 'open random file' code as when launching the
|
||||
// app from a file. This will also auto-dismiss the browser, returning
|
||||
// to the app's main screen.
|
||||
if let file = file {
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
_ = appDelegate.application(UIApplication.shared, open: file)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
updateAddress(request)
|
||||
return true
|
||||
}
|
||||
|
|
@ -222,13 +254,27 @@ class WebBrowserViewController: UIViewController, UIWebViewDelegate, UIAlertView
|
|||
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
|
||||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||||
updateButtons()
|
||||
let alertController = UIAlertController(title: "Cannot Open Page",
|
||||
message: error.localizedDescription,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: nil))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
var signalError: Bool = true
|
||||
|
||||
// An error will likely result if the user attempts to download a KMP,
|
||||
// despite the fact that we tell it not to attempt a load.
|
||||
let nsError = error as NSError
|
||||
if let url = nsError.userInfo["NSErrorFailingURLKey"] as? NSURL {
|
||||
signalError = !(url.path?.hasSuffix(".kmp") ?? false)
|
||||
}
|
||||
|
||||
if signalError {
|
||||
let alertController = UIAlertController(title: NSLocalizedString("error-opening-page", comment: ""),
|
||||
message: error.localizedDescription,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("command-ok",
|
||||
bundle: Bundle(for: Manager.self),
|
||||
comment: ""),
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: nil))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateButtons() {
|
||||
|
|
|
|||
|
|
@ -8,4 +8,27 @@
|
|||
|
||||
import XCGLogger
|
||||
|
||||
let log = XCGLogger(identifier: "Keyman", includeDefaultDestinations: true)
|
||||
// From XCGLogger docs:
|
||||
// Note: This creates the log object lazily, which means it's not created until it's actually needed.
|
||||
public let log: XCGLogger = {
|
||||
// Default: the 'console', which is read by Xcode but doesn't reach the system logs.
|
||||
let mainLog = XCGLogger(identifier: "Keyman", includeDefaultDestinations: false)
|
||||
|
||||
// Ensures our log messages go out to the device's system log as well as the console.
|
||||
let systemLogDest = AppleSystemLogDestination(identifier: "")
|
||||
systemLogDest.showLogIdentifier = true
|
||||
|
||||
mainLog.add(destination: systemLogDest)
|
||||
|
||||
// Temporary logging level to ensure that app details are reported properly.
|
||||
mainLog.outputLevel = .info
|
||||
mainLog.logAppDetails()
|
||||
|
||||
#if DEBUG
|
||||
mainLog.outputLevel = .debug
|
||||
#else
|
||||
mainLog.outputLevel = .warning
|
||||
#endif
|
||||
|
||||
return mainLog
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -116,9 +116,7 @@ class PackageBrowserViewController: UIDocumentPickerViewController, UIDocumentPi
|
|||
}
|
||||
|
||||
if let navVC = self.navVC {
|
||||
packageInstaller.promptForLanguages(inNavigationVC: navVC) {
|
||||
// do nothing; the Settings menu dismissal will take care of displaying the keyboard
|
||||
}
|
||||
packageInstaller.promptForLanguages(inNavigationVC: navVC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -21,13 +21,9 @@ class KeyboardViewController: InputViewController {
|
|||
// is enabled. They seem to get blocked otherwise, except in the Simulator.
|
||||
SentryManager.start(sendingEnabled: true)
|
||||
}
|
||||
_ = log
|
||||
_ = KeymanEngine.log
|
||||
|
||||
#if DEBUG
|
||||
KeymanEngine.log.outputLevel = .debug
|
||||
KeymanEngine.log.logAppDetails()
|
||||
#else
|
||||
KeymanEngine.log.outputLevel = .warning
|
||||
#endif
|
||||
Manager.applicationGroupIdentifier = "group.KM4I"
|
||||
|
||||
let bundle = Bundle(for: KeyboardViewController.self)
|
||||
|
|
|
|||
16
linux/.gitignore
vendored
16
linux/.gitignore
vendored
|
|
@ -1,28 +1,35 @@
|
|||
# linux
|
||||
.deps/
|
||||
autom4te.cache
|
||||
*.o
|
||||
*.orig
|
||||
Makefile.in
|
||||
configure
|
||||
aclocal.m4
|
||||
config.guess
|
||||
config.sub
|
||||
config.h
|
||||
config.h.in
|
||||
config.h.in~
|
||||
config.log
|
||||
config.rpath
|
||||
config.status
|
||||
compile
|
||||
depcomp
|
||||
install-sh
|
||||
libtool
|
||||
ltmain.sh
|
||||
missing
|
||||
test-driver
|
||||
ylwrap
|
||||
INSTALL
|
||||
Makefile
|
||||
ABOUT-NLS
|
||||
ltoptions.m4
|
||||
ltsugar.m4
|
||||
ltversion.m4
|
||||
lt~obsolete.m4
|
||||
libtool.m4
|
||||
stamp-h1
|
||||
__pycache__
|
||||
*.egg-info
|
||||
build-*
|
||||
|
|
@ -83,3 +90,10 @@ keyman-config/keyman_config/version.py
|
|||
keyman-config/locale/*/LC_MESSAGES/
|
||||
test.sh
|
||||
debianpackage/
|
||||
*.deb
|
||||
*.build
|
||||
*.buildinfo
|
||||
*.changes
|
||||
*.tar.xz
|
||||
*.tar.gz
|
||||
*.dsc
|
||||
|
|
@ -1,13 +1,21 @@
|
|||
# LICENSE
|
||||
|
||||
The [kmflcomp](./kmflcomp), [libkmfl](./libkmfl), and [keyman_config](./keyman_config) projects
|
||||
are covered by the [MIT license](./libkmfl/COPYING)
|
||||
are covered by the [MIT license](./libkmfl/COPYING).
|
||||
|
||||
The [ibus-kmfl](./ibus-kmfl) project is licensed under GNU General Public License as published
|
||||
by the Free Software Foundation; either [version 2 of the License](./ibus-kmfl/COPYING), or
|
||||
(at your option) any later version.
|
||||
The [ibus-kmfl](./ibus-kmfl) and [ibus-keyman](./ibus-keyman) projects are licensed under GNU
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
[version 2 of the License](./ibus-kmfl/COPYING), or (at your option) any later version.
|
||||
|
||||
Two files in ibus-kmfl, [kmflutil.c](./ibus-kmfl/src/kmflutil.c) and
|
||||
[kmflutil.h](./ibus-kmfl/src/kmflutil.h) are dual licensed by the MIT license and the
|
||||
GNU General Public License which is described above. The MIT license alone may be chosen for
|
||||
them for their use in other projects.
|
||||
[kmflutil.h](./ibus-kmfl/src/kmflutil.h), as well as four files in ibus-keyman,
|
||||
[keymanutil.c](./ibus-keyman/src/keymanutil.c), [keymanutil.h](./ibus-keyman/src/keymanutil.h),
|
||||
[kmpdetails.c](./ibus-keyman/src/kmpdetails.c) and [kmpdetails.h](./ibus-keyman/src/kmpdetails.h),
|
||||
are dual licensed by the MIT license and the GNU General Public License which is described above.
|
||||
The MIT license alone may be chosen for them for their use in other projects.
|
||||
|
||||
Two files in ibus-keyman, [keyman-service.c](./ibus-keyman/src/keyman-service.c) and
|
||||
[keyman-service.h](./ibus-keyman/src/keyman-service.h) are licensed under GNU
|
||||
General Public License version 3 as published by the Free Software Foundation; either
|
||||
[version 3 of the License](https://spdx.org/licenses/GPL-3.0-or-later.html), or (at your option)
|
||||
any later version.
|
||||
|
|
|
|||
4
linux/ibus-keyman/.gitignore
vendored
Normal file
4
linux/ibus-keyman/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
po/POTFILES
|
||||
src/keyman.xml
|
||||
src/keyman.xml.in
|
||||
src/ibus-engine-keyman
|
||||
|
|
@ -1 +1,3 @@
|
|||
Keyman engine for IBus.
|
||||
Keyman engine for IBus
|
||||
|
||||
This readme is required by autoconf. Otherwise see README.md.
|
||||
22
linux/ibus-keyman/README.md
Normal file
22
linux/ibus-keyman/README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Keyman engine for IBus
|
||||
|
||||
## Requirements
|
||||
|
||||
You need autoconf, autopoint, gettext, automake and libtool to generate the build system.
|
||||
|
||||
Run `./autogen.sh` to run them.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
./autogen.sh
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
For a debug build:
|
||||
|
||||
```bash
|
||||
./configure CPPFLAGS=-DG_MESSAGES_DEBUG CFLAGS="-g -O0" CXXFLAGS="-g -O0"
|
||||
```
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="inputmethod">
|
||||
<id>com.keyman.ibus_keyman</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<metadata_license>GPL-2+</metadata_license>
|
||||
<project_license>GPL-2+</project_license>
|
||||
<project_group>Keyman</project_group>
|
||||
<developer_name>SIL International</developer_name>
|
||||
<icon type="remote" width="96" height="96">https://keyman.com/cdn/dev/img/keyman-logo.png</icon>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Priority: optional
|
|||
Maintainer: Debian Input Method Team <debian-input-method@lists.debian.org>
|
||||
Uploaders:
|
||||
Keyman team <support@keyman.com>,
|
||||
Eberhard Beilharz <eb1@sil.org>,
|
||||
Build-Depends:
|
||||
debhelper (>= 11),
|
||||
libgtk-3-dev,
|
||||
|
|
@ -11,7 +12,7 @@ Build-Depends:
|
|||
libjson-glib-dev (>= 1.4.0),
|
||||
libkmnkbp-dev (>=11.0.100),
|
||||
pkg-config,
|
||||
Standards-Version: 4.5.0
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://github.com/keymanapp/keyman.git
|
||||
Vcs-Browser: https://github.com/keymanapp/keyman/tree/master/linux/ibus-keyman
|
||||
Homepage: https://www.keyman.com
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ Files: src/keyman-service.c
|
|||
Copyright: 2018-2021 SIL International
|
||||
License: GPL-3+
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2004-2021 SIL International
|
||||
License: GPL-2+
|
||||
|
||||
License: GPL-2+
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
|||
51
linux/ibus-keyman/debian/upstream/signing-key.asc
Normal file
51
linux/ibus-keyman/debian/upstream/signing-key.asc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=jcZL
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="inputmethod">
|
||||
<id>com.keyman.ibus_kmfl</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<metadata_license>GPL-2+</metadata_license>
|
||||
<project_license>GPL-2+</project_license>
|
||||
<project_group>Keyman</project_group>
|
||||
<developer_name>SIL International</developer_name>
|
||||
<icon type="remote" width="96" height="96">https://keyman.com/cdn/dev/img/keyman-logo.png</icon>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Priority: optional
|
|||
Maintainer: Debian Input Method Team <debian-input-method@lists.debian.org>
|
||||
Uploaders:
|
||||
Keyman team <support@keyman.com>,
|
||||
Eberhard Beilharz <eb1@sil.org>,
|
||||
Build-Depends:
|
||||
debhelper (>= 11),
|
||||
libibus-1.0-dev (>= 1.2),
|
||||
|
|
@ -12,7 +13,7 @@ Build-Depends:
|
|||
libx11-dev,
|
||||
pkg-config,
|
||||
x11proto-core-dev,
|
||||
Standards-Version: 4.5.0
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://github.com/keymanapp/keyman.git
|
||||
Vcs-Browser: https://github.com/keymanapp/keyman/tree/master/linux/ibus-kmfl
|
||||
Homepage: https://www.keyman.com
|
||||
|
|
|
|||
51
linux/ibus-kmfl/debian/upstream/signing-key.asc
Normal file
51
linux/ibus-kmfl/debian/upstream/signing-key.asc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=jcZL
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<!-- Copyright (c) 2019 Daniel Glassey <wdg@debian.org> -->
|
||||
<component type="desktop-application">
|
||||
<id>com.keyman.config</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<project_group>Keyman</project_group>
|
||||
<developer_name>SIL International</developer_name>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Priority: optional
|
|||
Maintainer: Debian Input Method Team <debian-input-method@lists.debian.org>
|
||||
Uploaders:
|
||||
Keyman team <support@keyman.com>,
|
||||
Eberhard Beilharz <eb1@sil.org>,
|
||||
Build-Depends:
|
||||
bash-completion,
|
||||
debhelper (>= 11),
|
||||
|
|
@ -23,13 +24,12 @@ Build-Depends:
|
|||
python3-requests,
|
||||
python3-requests-cache,
|
||||
python3-setuptools,
|
||||
Standards-Version: 4.5.0
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://github.com/keymanapp/keyman.git
|
||||
Vcs-Browser: https://github.com/keymanapp/keyman/tree/master/linux/keyman-config
|
||||
Homepage: https://www.keyman.com
|
||||
|
||||
Package: keyman
|
||||
Section: utils
|
||||
Architecture: all
|
||||
Depends:
|
||||
python3-keyman-config,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
#export DH_VERBOSE=1
|
||||
export PYBUILD_NAME=keyman-config
|
||||
export PYBUILD_INSTALL_ARGS=--install-scripts=/usr/share/keyman-config/
|
||||
|
|
@ -9,6 +11,7 @@ export PYBUILD_INSTALL_ARGS=--install-scripts=/usr/share/keyman-config/
|
|||
|
||||
override_dh_auto_build:
|
||||
make man
|
||||
sed -i -e "s/^__pkgversion__ = \"\"/__pkgversion__ = \"$(DEB_VERSION)\"/g" keyman_config/version.py
|
||||
make compile-po
|
||||
dh_auto_build $@
|
||||
|
||||
|
|
|
|||
51
linux/keyman-config/debian/upstream/signing-key.asc
Normal file
51
linux/keyman-config/debian/upstream/signing-key.asc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF46PusBEAC9veJYGsAAGnu5ug4RFELBXCwYTXK6aSaRFJ7ItW75EJUQWcIo
|
||||
FaDCWiiP9ZEXfS7JeG+RZ4MHp6pNnvNCX/Ww4QEfiI4NefMIon6cy2pWfKZRFBDG
|
||||
Y0x/9Mm+Wzx4vfuGP/TeFwkFy0syFYBsCkoV7KV3VxKNN73k6ZvkRn6tChebwVeM
|
||||
1FIldfeQw6KeTfdlE12wP4sI3MaaMh/QG1CnPRB4lTCK1WkpFqzxe3Y9XT6w+qxu
|
||||
tRV2xyAgbe19Q4SlDNvH4bgE/RlaWZ5zjnJLybP80t3qcEGKibUYEkQDuTPC44pn
|
||||
aqbRmEaihTHhMD/Ye0Oul1OQG13Io5jOm9OEKIzbwzOSlbZML+KVAIN85N1t0TW1
|
||||
N1gNZOZDwzXeHAUu+yKZdXliGM8Irb0nxQMWnZqIFtV6GQUNGskCUtd/tGcPe7Y6
|
||||
MXaPI7d5FLDGZmyUFsumazW0UN672PqHOlmrlwC+mSlmjFYPakg02Y8MiqPBZ5VB
|
||||
Uyf0YRBDYMc3WdmIxIYkgzjnSAuoDU3taSAFflS4AxfS5ebTw9ydxrrYEvZzPX4m
|
||||
hRpX2zt+DGfz/zovinO3kLn4Rch7g/ab6XCmQ/TbQ4GqTEq6XLnjXHSZLXr/Z+lD
|
||||
u3xtj9iAlxC48NspV/GGzIgsKJzTXXOfazIsgVjHDr/x+mmWZ9e5MwNu/wARAQAB
|
||||
tCFrZXltYW4tYmEgPGJ1aWxkYWdlbnRAa2V5bWFuLmNvbT6JAk4EEwEKADgWIQRs
|
||||
jureYH1DTHT9NP3I5hT1jxtl4AUCXjo+6wIbAwULCQgHAgYVCgkICwIEFgIDAQIe
|
||||
AQIXgAAKCRDI5hT1jxtl4LxHEAC4Lq+c48Q5q3UkfNxcGP2SkE++kco7haxnoQwT
|
||||
H1cJSqY+X+eHhAPYyhrriT3CwwQ3w5RT73v69tUrrpiIK6dc4ltQca0qrwppLzOc
|
||||
hZji61KWXYGHYIpqI+PpayxcJ1Vxz154EiFQk7Hbbo+x/5I0qzl6QmROHhenez06
|
||||
51hr5WGwonscnX7ReDuBA296XS5GCFQ1h+080xNkqiUt0SYWBhWzqQizEnF3VBrS
|
||||
Be5pNGqp5Md11In7ZsRNhGBs2ewpY0As6D9Ll0x4CulAAeMhXqbPnNhqE0uehbdR
|
||||
ks15fcBXwiVwUFN/dMacTFJovRgkXLU/c0OwHM6cnHBBHFDmKTohIYY8/g8eDt4m
|
||||
413XIJREOXaWa3UWrfwfup9FI/PI1g2KnGHEFP2PBS+77/3PVpQlYOnJ52Mc19ON
|
||||
BzAE5qFlXwLIyktcpvzaFz+mw5S5YvccjoLLRcTV9er/64ZhQxz87Sqn2l94wo4f
|
||||
V989CMfy2ujh4G80xcLJDDpovGQpdgflD7eZjaYyD1maYp+FlhYuDGT7nTzkX8nm
|
||||
G6RWICeHFJrk3OXyx3ir+IXSUc+B0NjQS/6rtTr5qCntm/R/WYnS5eDhwcForHB5
|
||||
KryJtkJBkIqZ8PalUWrSfSHWQjbJZgVEsJVKReBS0srW95j1Bi3gvsKLPf1/9zv2
|
||||
PmzVXLkCDQReOj7rARAAubQWo/upxgAUI4d8kGqIooyWgSbVbWnW4Ra/1FjP1RLn
|
||||
zChmJTYm5QTGEs2u2J0jhhdP/o2xYFdaD3BOVaDTCTV0Ron5Y2EI5T0WEqLuGcu0
|
||||
YmaGtR0fj1qw/0IJ1CtOfv3V0XIGuSM2NQtCiS80oo1sRBwpi+eDWn19UClT7F0T
|
||||
PVwX2eXwgb1UR6CWbxu4nUMyq0lTfYyk2L+XiC/gbtVLs4I2UaxvO0DOxPbEoKyK
|
||||
SUA5Jxae/QsLrJ4HvqlGekgqSH5gLAr6zH1jWKn0bnwRX92ka/yTIzQQg5+vgJQH
|
||||
inOOhIg5Gii55KgQjLL7atL48w/Df9ftJohm9LN6YUzF5qCSAs1BQ7p3G2LKR8Vw
|
||||
CMYH9Fp6dHeb/83w6pSth/kYfx/GyI5tXCuCCzgTvEFW9/qjhvSNk6hW7NhwmiRz
|
||||
oqPnOub2NewgIu3EBgme7f5U6M/zAtTGoJn6+ftfILsW9SxKJgZWkxzmndvZ/hZn
|
||||
SAGmBQNylq0/xGKjGjoeKbkR0oNlUGYWJ4svFrd4VFk65a502JdTU5ZxoJGFKGgh
|
||||
UNWvqqL0ruTQmtXWFOfZdCKOIbHjoTP/xsnmnKIbPpe09J3kgJc6zsplHEPO5dXJ
|
||||
kfD4oTQQsrUG3dmNBJU3jbSV0jcHzrMGRTpYw2+ptsk/PWJc9XSUpJAAQP+/mvUA
|
||||
EQEAAYkCNgQYAQoAIBYhBGyO6t5gfUNMdP00/cjmFPWPG2XgBQJeOj7rAhsMAAoJ
|
||||
EMjmFPWPG2Xg4D8QAJr7v8Ly2DYd7e3ilk3LWURXpvi79U5fsy3qLglZt/8c52mf
|
||||
DGo+s3XjVhVqFMYlhs1ezmVPgORtDky+57aS5cgPU5Lqo4fYZoLwDS0LUvMEO5Im
|
||||
pTo/7rtdRpVsmSgyJ48t0eh3qi/mf8ONFqho53elb5IdROrpM1pZm3vWvM/vdEMi
|
||||
kGN1R6BdajKEvofdc2x/3YQhQyfekCxKa/RXRQ5M+JtW4iHrDuyAFksoCVk2p4dO
|
||||
OdZoNaMMYi6egcHbph+LR2CYsnijUr7vlTOBfF2XUom+vq5audySc4+NqeaWyXtD
|
||||
mmoriz8IF0hIt2mWpQCqC/gftR9K2SDd8TYn2Z8OH4/h8cLRQssN/VsZie3jDh4u
|
||||
ZD+A/1IF5q+Ayx+pXRpoDMDl+vvHHBfHpnLt4iFuPNZgJO5xSBd5Ra6/PRPs26wV
|
||||
y+ArzNhdA5ImhOPHF08qwBxDeS7476N7j3yTGv1vPJTOmIII1/60bUI/u8cAjXWa
|
||||
tMHDsIAIfJF2A8bkukbNEQUcWw3HUIA3lCq7cV6xs9LT5/xa1XfMQoj9vHMogCcT
|
||||
YykbTr9QHLLX62htZijKU1REYuxwEoFXIm7y7HpBIWNBjSLkE53K9RaZP3z01xcX
|
||||
B0e/y1m02FicU+owqJC38pXpUogyw+ZLj+VNFbv/dSDaG+00eRdqGGmTTIgo
|
||||
=jcZL
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
|
@ -9,6 +9,8 @@ from .version import __versionwithtag__
|
|||
from .version import __majorversion__
|
||||
from .version import __releaseversion__
|
||||
from .version import __tier__
|
||||
from .version import __pkgversion__
|
||||
from .version import __environment__
|
||||
|
||||
|
||||
def _(txt):
|
||||
|
|
@ -36,6 +38,8 @@ KeymanDownloadsUrl = 'https://downloads.keyman.com'
|
|||
|
||||
if 'unittest' in sys.modules.keys():
|
||||
print('Not reporting to Sentry')
|
||||
elif os.environ.get('KEYMAN_NOSENTRY'):
|
||||
print('Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set')
|
||||
else:
|
||||
try:
|
||||
# Try new sentry-sdk first
|
||||
|
|
@ -46,13 +50,15 @@ else:
|
|||
SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@sentry.keyman.com/12"
|
||||
sentry_sdk.init(
|
||||
dsn=SentryUrl,
|
||||
environment=__tier__,
|
||||
environment=__environment__,
|
||||
release=__version__,
|
||||
)
|
||||
with configure_scope() as scope:
|
||||
scope.set_tag("app", os.path.basename(sys.argv[0]))
|
||||
scope.set_tag("pkgversion", __pkgversion__)
|
||||
scope.set_tag("platform", platform.platform())
|
||||
scope.set_tag("system", platform.system())
|
||||
scope.set_tag("tier", __tier__)
|
||||
except ImportError:
|
||||
try:
|
||||
# sentry-sdk is not available, so use older raven
|
||||
|
|
@ -61,11 +67,13 @@ else:
|
|||
HaveSentryNewSdk = False
|
||||
|
||||
SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357:e6d5a81ee6944fc79bd9f0cbb1f2c2a4@sentry.keyman.com/12"
|
||||
client = Client(SentryUrl, environment=__tier__, release=__version__)
|
||||
client = Client(SentryUrl, environment=__environment__, release=__version__)
|
||||
client.tags_context({
|
||||
'app': os.path.basename(sys.argv[0]),
|
||||
'pkgversion': __pkgversion__,
|
||||
'platform': platform.platform(),
|
||||
'system': platform.system(),
|
||||
'tier': __tier__,
|
||||
})
|
||||
except ImportError:
|
||||
# even raven is not available. This is the case on Ubuntu 16.04. Just ignore.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue