mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-13 17:37:31 +00:00
Merge branch 'beta' into fix/windows/12660/preserve-report-usage
This commit is contained in:
commit
c39379bbe9
101 changed files with 1027 additions and 498 deletions
35
HISTORY.md
35
HISTORY.md
|
|
@ -1,5 +1,40 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 18.0.195 beta 2025-02-18
|
||||
|
||||
* docs(web): use promise-chaining in manual-control example (#13272)
|
||||
|
||||
## 18.0.194 beta 2025-02-17
|
||||
|
||||
* fix(android): Use TIER.md to determine Play Store tier (#13259)
|
||||
* fix(android): prevent update-checks from crashing system keyboard and app if `DownloadManager` is disabled (#13218)
|
||||
* feat(linux): add `--remote-debug` flag to `run-tests.sh` script (#13233)
|
||||
* chore(linux): improve output when running tests (#13247)
|
||||
* refactor(linux): cleanup code of test fixture (#13248)
|
||||
|
||||
## 18.0.193 beta 2025-02-14
|
||||
|
||||
* chore(developer): add INFO messages for `kmc copy` and `kmc generate` (#13200)
|
||||
* fix(android): Remove sendOptionsToKeyboard function from Engine API (#13219)
|
||||
* docs(android): Document removing sendOptionsToKeyboard API (#13220)
|
||||
* chore(ios): increase minimum ios version to 15 (#13201)
|
||||
* fix(android): Catch WebView exceptions and prompt user to install WebView (#13140)
|
||||
* chore(common): Update crowdin strings for Mon (#13241)
|
||||
* chore(ios,windows): Update Crowdin strings for Czech (#13242)
|
||||
* change(web): drop 'dom' unit test concurrency when run in CI mode (#13141)
|
||||
* chore(web): ensure /web/build.sh clean triggers all predictive-text child actions (#13158)
|
||||
* chore(web): fix coverage-reporting artifact (#13196)
|
||||
|
||||
## 18.0.192 beta 2025-02-14
|
||||
|
||||
* chore(common): Fix 17.0.335 tier in HISTORY.md (#13213)
|
||||
* docs(linux): update what's new for 18.0 (#13207)
|
||||
* chore(linux): additional code cleanup after Focal removal (#13206)
|
||||
* docs(windows): update keyman for windows 18.0 whats-new (#13211)
|
||||
* chore: undo master merge (#13238)
|
||||
* change(ios): prevent memory leaks from keyboard-related notification observers (#13129)
|
||||
* change(ios): mitigate keyboard input-view memory leak by force-discarding WebView (#13169)
|
||||
|
||||
## 18.0.191 beta 2025-02-12
|
||||
|
||||
* feat(windows): handle a hard windows reset occurring while downloading updated keyman files (#13128)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
18.0.192
|
||||
18.0.196
|
||||
|
|
@ -67,6 +67,7 @@ android {
|
|||
productFlavors {
|
||||
}
|
||||
|
||||
String tier = new File('../../TIER.md').getText('UTF-8').trim();
|
||||
applicationVariants.all { variant ->
|
||||
// Append tag to app name to help differentiate versions for app testers:
|
||||
// - stable builds have no suffix
|
||||
|
|
@ -75,7 +76,7 @@ android {
|
|||
// - PR builds also have "-test-1234" suffix (where 1234 is PR#)
|
||||
// Gradle sync will nullify these values, so have a fallback
|
||||
String appSuffix = ''
|
||||
String tag = VERSION_TAG ? VERSION_TAG : '-' + new File('../../TIER.md').getText('UTF-8');
|
||||
String tag = VERSION_TAG ? VERSION_TAG : '-' + tier;
|
||||
if (tag.startsWithAny("-beta", "beta")) {
|
||||
appSuffix = ' Beta';
|
||||
} else if (tag.startsWithAny("-alpha", "alpha")) {
|
||||
|
|
@ -127,7 +128,7 @@ if (env_keys_json_file != null && file(env_keys_json_file).exists()) {
|
|||
|
||||
// Deactivate lower conflicting version APKs
|
||||
resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.IGNORE)
|
||||
switch (System.env.TIER) {
|
||||
switch (String.valueOf(tier)) {
|
||||
case 'beta':
|
||||
track.set("beta")
|
||||
println "TIER set to beta"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package com.keyman.android;
|
||||
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
|
||||
import com.tavultesoft.kmapro.BuildConfig;
|
||||
import com.tavultesoft.kmapro.DefaultLanguageResource;
|
||||
|
|
@ -84,7 +85,12 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false);
|
||||
KMManager.setHapticFeedback(mayHaveHapticFeedback);
|
||||
|
||||
KMManager.executeResourceUpdate(this);
|
||||
// Checking for updates should never be allowed to crash the keyboard.
|
||||
// Just silently fail if this occurs.
|
||||
if(DownloadFileUtils.getDownloadManager(this) != null) {
|
||||
// Will try to emit a toast if it fails - i.e., is not silent.
|
||||
KMManager.executeResourceUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -250,8 +256,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
if (exText != null)
|
||||
exText = null;
|
||||
}
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ public class AdjustLongpressDelayActivity extends BaseActivity {
|
|||
// Store the longpress delay as a reference
|
||||
// and then update KeymanWeb with the longpress delay
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -70,7 +71,13 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_kmp_browser);
|
||||
try {
|
||||
setContentView(R.layout.activity_kmp_browser);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// WebView not enabled
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
webView = (WebView) findViewById(R.id.kmpBrowserWebView);
|
||||
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ import com.keyman.android.DownloadIntentService;
|
|||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.KMPLink;
|
||||
import com.keyman.engine.util.KMString;
|
||||
import com.keyman.engine.util.WebViewUtils;
|
||||
import com.keyman.engine.util.WebViewUtils.EngineWebViewVersionStatus;
|
||||
import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ProgressDialog;
|
||||
|
|
@ -153,7 +155,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
KMManager.setDebugMode(true);
|
||||
}
|
||||
|
||||
// Verify WebView installed and enabled before attempting to initialize KMManager
|
||||
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
|
||||
KMManager.executeResourceUpdate(this);
|
||||
|
||||
DefaultLanguageResource.install(context);
|
||||
|
|
@ -181,8 +185,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
textView.setTextSize((float) textSize);
|
||||
textView.setSelection(textView.getText().length());
|
||||
|
||||
// Use in-app keyboard WebView to check the Chrome version
|
||||
checkChromeVersion(KMManager.getKMKeyboard(KeyboardType.KEYBOARD_TYPE_INAPP));
|
||||
// Check WebView enabled and Chrome version
|
||||
SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context);
|
||||
EngineWebViewVersionStatus chromeStatus = KMManager.getEngineWebViewVersionStatus();
|
||||
updateWebViewIfNeeded(chromeStatus, webViewStatus);
|
||||
|
||||
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
|
||||
checkGetStarted();
|
||||
}
|
||||
|
|
@ -475,8 +482,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
@Override
|
||||
public void onKeyboardLoaded(KeyboardType keyboardType) {
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -770,39 +776,114 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
KMManager.setHapticFeedback(mayHaveHapticFeedback);
|
||||
}
|
||||
|
||||
private void checkChromeVersion(WebView webView) {
|
||||
if (KMManager.getEngineWebViewVersionStatus() != EngineWebViewVersionStatus.FULL) {
|
||||
LinearLayout updateChromeLayout = (LinearLayout) findViewById(R.id.updateChromeLayout);
|
||||
updateChromeLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
Button updateChromeButton = (Button)findViewById(R.id.updateChromeButton);
|
||||
updateChromeButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Launch PlayStore to update Chrome
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
// Link to Chrome if user is not signed in to Play Store
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Based on whether Chrome and WebView are installed & enabled,
|
||||
* display the corresponding update panel
|
||||
* @param chromeStatus
|
||||
* @param webViewStatus
|
||||
*/
|
||||
private void updateWebViewIfNeeded(EngineWebViewVersionStatus chromeStatus, SystemWebViewStatus webViewStatus) {
|
||||
if (chromeStatus == EngineWebViewVersionStatus.FULL && webViewStatus == SystemWebViewStatus.FULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (webViewStatus == SystemWebViewStatus.NOT_INSTALLED) {
|
||||
displayInstallWebView();
|
||||
} else if (webViewStatus == SystemWebViewStatus.DISABLED) {
|
||||
displayEnableWebView();
|
||||
} else {
|
||||
// Otherwise, display update Chrome
|
||||
displayUpdateChrome();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayInstallWebView() {
|
||||
TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
|
||||
textView.setText(getString(R.string.body_install_webview));
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.label_install_webview));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Play Store link to install WebView
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.webview"));
|
||||
|
||||
// Launch intent
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void displayEnableWebView() {
|
||||
TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
|
||||
textView.setText(getString(R.string.body_enable_webview));
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.label_enable_webview));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Application settings to enable WebView
|
||||
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS",
|
||||
Uri.parse("package:com.google.android.webview"));
|
||||
|
||||
// Launch intent
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void displayUpdateChrome() {
|
||||
// TextView's default string is to update Chrome
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.button_update_chrome));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Primary Play Store link to install/update Chrome
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=com.android.chrome"));
|
||||
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
// (alternate Play Store link to install/update Chrome)
|
||||
intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
|
||||
intent.resolveActivity(packageManager);
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private Uri requestPermissionIntentUri;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<include layout="@layout/titlebar" />
|
||||
|
||||
<include layout="@layout/update_chrome_layout"
|
||||
<include layout="@layout/check_chrome_webview_layout"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
android:background="@drawable/textview_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/updateChromeLayout"
|
||||
android:layout_below="@id/checkWebViewChromeLayout"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine|textNoSuggestions"
|
||||
android:hint="@string/textview_hint"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/updateChromeLayout"
|
||||
android:id="@+id/checkWebViewChromeLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Banner to display if old version of Chrome installed -->
|
||||
<!-- Banner to display if WebView is not installed/enabled or old version of Chrome installed -->
|
||||
<TextView
|
||||
android:id="@+id/kmUpgradeChromeTextView"
|
||||
android:id="@+id/kmWebViewChromeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
android:textSize="@dimen/update_chrome_label_textsize" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/updateChromeButton"
|
||||
android:id="@+id/webViewChromeButton"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -67,6 +67,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">ချိၚ်ဆသမၠုၚ်ကဳဗုဒ်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">ချိၚ်အခိၚ် ဍဵုလအ်အ်</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">က္တိုပ်လိက် Spacebar</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">ကဳဗုဒ်</string>
|
||||
|
|
@ -87,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">လ္ပဓမံက်ထ္ၜးမလိက်ပ္ဍဲspacebarညိ</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">ပ္ဍဲကာလမတက်လိက် ချဳဒတဝ်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">ဓမံက်ထ္ၜးbannerလၟိုန်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">သွက်ဂွံဓမံက်ရုပ်ရဴ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">ကၟာတ်လဝ်မ္ဂး မလိက်ချိၚ်ဆလဝ်တံဂှ် ထ္ၜးအဃောပံက်လဝ်ဟေၚ်</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">ကဵုအခေါၚ် ပၠံၚ်လိက်စဳရေၚ်လ္တူ အေန်တာနက်ညိ</string>
|
||||
|
|
@ -123,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">ဂွံချိၚ် လံက်သတိက် လံက်ဇမၠိၚ်ဂှ် ဗဂေတ်ကဵုစက်ပိုဲညိ။</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">ကလေၚ်သ္ပနူပကတိ</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">အခိၚ်ဖအိၚ်: %1$.1f စက္က</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">ဖအိၚ်အခိၚ်လအ်အ်</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">ဖအိၚ်အခိၚ်အောန်န်</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">ဍဵုလအ်အ်သွက် မသရေတ််ဖအိၚ်အခိၚ်</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">ဂၠာဲ ဟွံသေၚ် တက် URL</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
<!-- Context: Menu Action -->
|
||||
<string name="action_install_updates" comment="Menu notification that keyboard or dictionary updates available">Install Updates</string>
|
||||
|
||||
|
||||
<!-- Context: Title -->
|
||||
<string name="title_version" comment="Title of Keyman for Android version">Version: %1$s</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@
|
|||
</intent>
|
||||
</queries>
|
||||
|
||||
<queries>
|
||||
<package android:name="com.android.webkit" />
|
||||
<package android:name="com.google.android.ims" />
|
||||
</queries>
|
||||
|
||||
<!-- https://developer.android.com/develop/ui/views/notifications/notification-permission#declare -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ var bannerImagePath = '';
|
|||
var bannerHTMLContents = '';
|
||||
var fragmentToggle = 0;
|
||||
var deferredBannerCall;
|
||||
var longpressDelay = null;
|
||||
|
||||
var sentryManager = new KeymanSentryManager({
|
||||
hostPlatform: "android"
|
||||
|
|
@ -71,6 +72,10 @@ function init() {
|
|||
|
||||
keyman.refreshOskLayout();
|
||||
}
|
||||
// Initialize the longpress delay
|
||||
if(longpressDelay !== null) {
|
||||
setLongpressDelay(longpressDelay);
|
||||
}
|
||||
});
|
||||
|
||||
keyman.addEventListener('keyboardloaded', setIsChiral);
|
||||
|
|
@ -128,11 +133,10 @@ function notifyHost(event, params) {
|
|||
// Update the KeymanWeb longpress delay
|
||||
// delay is in milliseconds
|
||||
function setLongpressDelay(delay) {
|
||||
if (keyman.osk) {
|
||||
longpressDelay = delay;
|
||||
if (keyman && keyman.osk) {
|
||||
keyman.osk.gestureParams.longpress.waitLength = delay;
|
||||
console_debug('setLongpressDelay('+delay+')');
|
||||
} else {
|
||||
window.console.log('setLongpressDelay error: keyman.osk undefined');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import android.content.res.Configuration;
|
|||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
|
@ -110,14 +111,29 @@ final class KMKeyboard extends WebView {
|
|||
|
||||
public String specialOskFont = "";
|
||||
|
||||
public KMKeyboard(Context context) {
|
||||
/**
|
||||
* Constructor for KMKeyboard.
|
||||
* If System WebView not installed/enabled, will throw AndroidRuntimeException.
|
||||
* Can't catch the exception here because super(context) must be the first line.
|
||||
* @param context
|
||||
* @throws AndroidRuntimeException
|
||||
*/
|
||||
public KMKeyboard(Context context) throws AndroidRuntimeException {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.keyboardType = KeyboardType.KEYBOARD_TYPE_INAPP;
|
||||
initKMKeyboard(context);
|
||||
}
|
||||
|
||||
public KMKeyboard(Context context, KeyboardType keyboardType) {
|
||||
/**
|
||||
* Constructor for KMKeyboard.
|
||||
* If System WebView not installed/enabled, will throw AndroidRuntimeException.
|
||||
* Can't catch the exception here because super(context) must be the first line.
|
||||
* @param context {Context}
|
||||
* @param keyboardType {KeyboardType}
|
||||
* @throws AndroidRuntimeException
|
||||
*/
|
||||
public KMKeyboard(Context context, KeyboardType keyboardType) throws AndroidRuntimeException {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.keyboardType = keyboardType;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class KMKeyboardDownloaderActivity extends BaseActivity {
|
|||
|
||||
//TODO: move to keyboard manager class
|
||||
private static ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> kbDownloadEventListeners = null;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import android.net.NetworkInfo;
|
|||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputType;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
|
@ -73,6 +74,7 @@ import com.keyman.engine.util.KMLog;
|
|||
import com.keyman.engine.util.KMString;
|
||||
import com.keyman.engine.util.MapCompat;
|
||||
import com.keyman.engine.util.WebViewUtils;
|
||||
import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
|
@ -687,12 +689,32 @@ public final class KMManager {
|
|||
KMKeyboardWebViewClient webViewClient = null;
|
||||
|
||||
if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) {
|
||||
InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
try {
|
||||
InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// Catch fatal error when WebView not installed/enabled
|
||||
// Only log exceptions unrelated to WebView
|
||||
if (e != null && !e.getMessage().contains("MissingWebViewPackageException")) {
|
||||
KMLog.LogException(TAG, "initKeyboard for INAPP" , e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType);
|
||||
keyboard = InAppKeyboard;
|
||||
webViewClient = InAppKeyboardWebViewClient;
|
||||
} else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) {
|
||||
SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
try {
|
||||
SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// Catch fatal error when WebView not installed/enabled
|
||||
// Catch fatal error when WebView not installed/enabled
|
||||
// Only log exceptions unrelated to WebView
|
||||
if (e != null && !e.getMessage().contains("MissingWebViewPackageException")) {
|
||||
KMLog.LogException(TAG, "initKeyboard for SYSTEM" , e);
|
||||
}
|
||||
// TODO: Send user to pick another system keyboard
|
||||
return;
|
||||
}
|
||||
SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType);
|
||||
keyboard = SystemKeyboard;
|
||||
webViewClient = SystemKeyboardWebViewClient;
|
||||
|
|
@ -722,6 +744,7 @@ public final class KMManager {
|
|||
}
|
||||
keyboard.setBanner(KMManager.BannerType.HTML);
|
||||
keyboard.showBanner(true);
|
||||
sendLongpressDelay();
|
||||
}
|
||||
setEngineWebViewVersionStatus(appContext, keyboard);
|
||||
}
|
||||
|
|
@ -776,6 +799,10 @@ public final class KMManager {
|
|||
final FrameLayout mainLayout = new FrameLayout(appContext);
|
||||
mainLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
|
||||
if (SystemKeyboard == null) {
|
||||
return mainLayout;
|
||||
}
|
||||
|
||||
RelativeLayout keyboardLayout = new RelativeLayout(appContext);
|
||||
keyboardLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
ViewGroup parent = (ViewGroup) SystemKeyboard.getParent();
|
||||
|
|
@ -921,7 +948,7 @@ public final class KMManager {
|
|||
copyAsset(context, KMFilename_KmwCss, "", true);
|
||||
copyAsset(context, KMFilename_KmwGlobeHintCss, "", true);
|
||||
copyAsset(context, KMFilename_Osk_Ttf_Font, "", true);
|
||||
|
||||
|
||||
// Needed until our minimum version of Chrome is 61.0+.
|
||||
copyAsset(context, KMFilename_JSPolyfill2, "", true);
|
||||
|
||||
|
|
@ -2124,7 +2151,7 @@ public final class KMManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the longpress delay (in milliseconds) as a stored preference.
|
||||
* Set the longpress delay (in milliseconds) as a stored preference and sends to KeymanWeb.
|
||||
* Valid range is 300 ms to 1500 ms. Returns true if the preference is successfully stored.
|
||||
* @param longpressDelay - int longpress delay in milliseconds
|
||||
* @return boolean
|
||||
|
|
@ -2140,17 +2167,19 @@ public final class KMManager {
|
|||
editor.putInt(KMKey_LongpressDelay, longpressDelay);
|
||||
editor.commit();
|
||||
|
||||
// Send longpress delay to KeymanWeb
|
||||
sendLongpressDelay();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends options to the KeymanWeb keyboard.
|
||||
* Sends longpress delay to the KeymanWeb keyboard.
|
||||
* 1. number of milliseconds to trigger a longpress gesture.
|
||||
* This method requires a keyboard to be loaded for the value to take effect.
|
||||
*/
|
||||
public static void sendOptionsToKeyboard() {
|
||||
private static void sendLongpressDelay() {
|
||||
int longpressDelay = getLongpressDelay();
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (InAppKeyboard != null) {
|
||||
InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
|
||||
}
|
||||
|
||||
|
|
@ -2301,9 +2330,9 @@ public final class KMManager {
|
|||
public static boolean updateText(KeyboardType kbType, String text) {
|
||||
boolean result = false;
|
||||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) {
|
||||
return InAppKeyboard.updateText(text);
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) {
|
||||
return SystemKeyboard.updateText(text);
|
||||
}
|
||||
|
||||
|
|
@ -2335,13 +2364,13 @@ public final class KMManager {
|
|||
public static boolean updateSelectionRange(KeyboardType kbType) {
|
||||
boolean result = false;
|
||||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = InAppKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
||||
InAppKeyboard.setShouldIgnoreSelectionChange(false);
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = SystemKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
|
@ -2354,11 +2383,11 @@ public final class KMManager {
|
|||
|
||||
public static void resetContext(KeyboardType kbType) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && InAppKeyboard != null) {
|
||||
InAppKeyboard.loadJavascript("resetContext()");
|
||||
}
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && SystemKeyboard != null) {
|
||||
SystemKeyboard.loadJavascript("resetContext()");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) {
|
||||
activeView = v;
|
||||
if (KMManager.InAppKeyboardWebViewClient.getKeyboardLoaded()) {
|
||||
if (KMManager.InAppKeyboard != null && KMManager.InAppKeyboardWebViewClient.getKeyboardLoaded()) {
|
||||
updateTextContext();
|
||||
}
|
||||
showKeyboard();
|
||||
|
|
@ -165,7 +165,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
if (activeView != null && activeView.equals(this)) {
|
||||
if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (KMManager.InAppKeyboard != null && KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
AppCompatActivity activity = (AppCompatActivity)context;
|
||||
|
||||
Window mainWindow = activity.getWindow();
|
||||
if (hasWindowFocus) {
|
||||
if (hasWindowFocus && KMManager.InAppKeyboard != null) {
|
||||
KMManager.InAppKeyboardWebViewClient.setContext(context);
|
||||
activeView = mainWindow.getCurrentFocus();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keyman.engine.R;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -206,12 +210,34 @@ public class CloudDownloadMgr{
|
|||
* @param params the cloud api params for download
|
||||
* @param <ModelType> the target models type
|
||||
* @param <ResultType> the cloud requests result type
|
||||
* @return `false` if the download request cannot be executed; `true` otherwise.
|
||||
*/
|
||||
public <ModelType,ResultType> void executeAsDownload(Context aContext, String aDownloadIdentifier,
|
||||
public <ModelType,ResultType> boolean executeAsDownload(Context aContext, String aDownloadIdentifier,
|
||||
ModelType aTargetModel,
|
||||
ICloudDownloadCallback<ModelType,ResultType> aCallback,
|
||||
CloudApiTypes.CloudApiParam... params)
|
||||
{
|
||||
CloudApiTypes.CloudApiParam... params) {
|
||||
try {
|
||||
executeAsDownloadInternal(aContext, aDownloadIdentifier, aTargetModel, aCallback, params);
|
||||
} catch (DownloadManagerDisabledException e) {
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.update_check_download_manager_disabled),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.update_check_unavailable),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
KMLog.LogException(TAG, "Unexpected exception occurred during download/query attempt", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private <ModelType,ResultType> void executeAsDownloadInternal(Context aContext, String aDownloadIdentifier,
|
||||
ModelType aTargetModel,
|
||||
ICloudDownloadCallback<ModelType,ResultType> aCallback,
|
||||
CloudApiTypes.CloudApiParam... params) throws DownloadManagerDisabledException {
|
||||
if(!isInitialized) {
|
||||
Log.w(TAG, "DownloadManager not initialized. Initializing CloudDownloadMgr.");
|
||||
initialize(aContext);
|
||||
|
|
@ -219,16 +245,22 @@ public class CloudDownloadMgr{
|
|||
KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.executeAsDownload() called; already initialized", true);
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = DownloadFileUtils.getDownloadManager(aContext);
|
||||
if(downloadManager == null) {
|
||||
// The callback object provided to us provides no way to directly signal a
|
||||
// failure. That said, we can also immediately detect that we WILL fail and
|
||||
// corresponding error _now_, rather than later.
|
||||
//
|
||||
// Unique custom error so it's easy to explicitly filter.
|
||||
throw new DownloadManagerDisabledException();
|
||||
}
|
||||
|
||||
synchronized (downloadSetByDownloadIdentifier) {
|
||||
|
||||
if (alreadyDownloadingData(aDownloadIdentifier) || params == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
if(downloadManager==null)
|
||||
throw new IllegalStateException("DownloadManager is not available");
|
||||
|
||||
aCallback.initializeContext(aContext);
|
||||
|
||||
CloudApiTypes.CloudDownloadSet<ModelType,ResultType> _downloadSet =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.keyman.engine.cloud;
|
||||
|
||||
public class DownloadManagerDisabledException extends RuntimeException {
|
||||
DownloadManagerDisabledException() {
|
||||
super("System service DownloadManager is not available and cannot facilitate downloads or queries.");
|
||||
}
|
||||
}
|
||||
|
|
@ -111,8 +111,8 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
|
|||
|
||||
BaseActivity.makeToast(aContext, R.string.dictionary_download_start_in_background, Toast.LENGTH_SHORT);
|
||||
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(aContext,
|
||||
_r.additionalDownloadid, null, _callback,
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
aContext, _r.additionalDownloadid, null, _callback,
|
||||
_r.additionalDownloads.toArray(new CloudApiTypes.CloudApiParam[0]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ import com.keyman.engine.KMManager;
|
|||
import com.keyman.engine.KeyboardPickerActivity;
|
||||
import com.keyman.engine.R;
|
||||
import com.keyman.engine.cloud.CloudApiTypes;
|
||||
import com.keyman.engine.cloud.DownloadManagerDisabledException;
|
||||
import com.keyman.engine.cloud.impl.CloudCatalogDownloadCallback;
|
||||
import com.keyman.engine.cloud.impl.CloudCatalogDownloadReturns;
|
||||
import com.keyman.engine.cloud.CloudDataJsonUtil;
|
||||
import com.keyman.engine.cloud.CloudDownloadMgr;
|
||||
import com.keyman.engine.packages.JSONUtils;
|
||||
import com.keyman.engine.util.BCP47;
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.VersionUtils;
|
||||
|
||||
|
|
@ -366,6 +368,10 @@ public class CloudRepository {
|
|||
* @param onFailure A callback to be triggered upon failure of a query.
|
||||
*/
|
||||
private void downloadMetaDataFromServer(@NonNull Context context, UpdateHandler updateHandler, Runnable onSuccess, Runnable onFailure) {
|
||||
if(DownloadFileUtils.getDownloadManager(context) == null) {
|
||||
onFailure.run();
|
||||
return;
|
||||
}
|
||||
boolean cacheValid = getCacheValidity(context);
|
||||
|
||||
// For local and PR test builds, force download of metadata
|
||||
|
|
@ -405,8 +411,13 @@ public class CloudRepository {
|
|||
BaseActivity.makeToast(context, R.string.catalog_download_is_running_in_background, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
updateIsRunning = true;
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
boolean executionStarted = CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
|
||||
if(!executionStarted) {
|
||||
// Since we couldn't initiate the execution's async components,
|
||||
// we need to immediately clear the update-running flag.
|
||||
updateIsRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
return;
|
||||
}
|
||||
|
||||
// Warning: can be run by our system keyboard, which will attempt to
|
||||
// display the toast if it or the app is visible!
|
||||
BaseActivity.makeToast(currentContext, R.string.update_check_unavailable, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
updateCheckFailed = true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
*/
|
||||
package com.keyman.engine.util;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
|
|
@ -21,6 +23,37 @@ import java.io.InputStream;
|
|||
public final class DownloadFileUtils {
|
||||
private static final String TAG = "DownloadFileUtils";
|
||||
|
||||
private static final String DOWNLOAD_MANAGER_PACKAGE_NAME = "com.android.providers.downloads";
|
||||
|
||||
/**
|
||||
* Determines whether or not Android's `DownloadManager` service is active, only
|
||||
* returning an instance of DownloadManager when it is currently accessible and enabled.
|
||||
* Downloads and cloud queries are impossible when it's disabled.
|
||||
*
|
||||
* This solution is based heavily on
|
||||
* https://gist.github.com/Folyd/b9412bb6e2b06eb511f7.
|
||||
* @return A valid and enabled reference to the system's DownloadManager service. May be null
|
||||
* if it is not accessible or is disabled.
|
||||
*/
|
||||
public static DownloadManager getDownloadManager(Context context) {
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
if(downloadManager==null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int state = context.getPackageManager().getApplicationEnabledSetting(DOWNLOAD_MANAGER_PACKAGE_NAME);
|
||||
|
||||
if(
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER ||
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|
||||
) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return downloadManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small class for returning information about a file downloaded via DownloadManager.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
package com.keyman.engine.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -22,6 +24,14 @@ public final class WebViewUtils {
|
|||
FULL; // WebView supports touch keyboards and LDML keyboards
|
||||
}
|
||||
|
||||
// System WebView status
|
||||
public enum SystemWebViewStatus {
|
||||
UNDETERMINED, // Functionality not determined yet
|
||||
NOT_INSTALLED, // WebView not installed
|
||||
DISABLED, // WebView installed, but disabled
|
||||
FULL; // WebView installed and enabled
|
||||
}
|
||||
|
||||
private static final String CHROME_INSTALL_PATTERN_FORMATSTR = "^.*Chrome/([\\d.]+).*$";
|
||||
private static final Pattern installPattern = Pattern.compile(CHROME_INSTALL_PATTERN_FORMATSTR);
|
||||
|
||||
|
|
@ -51,6 +61,34 @@ public final class WebViewUtils {
|
|||
return EngineWebViewVersionStatus.FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the state of the system WebView (required for Keyman)
|
||||
* @param context
|
||||
* @return {SystemWebViewStatus}
|
||||
*/
|
||||
public static SystemWebViewStatus getSystemWebViewStatus(
|
||||
Context context) {
|
||||
// Check whether WebView is installed
|
||||
// If the package is installed but disabled, hasSystemFeature() still returns true
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (!packageManager.hasSystemFeature("android.software.webview")) {
|
||||
return SystemWebViewStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
// Use CookieManager to check if WebView is really enabled
|
||||
// https://stackoverflow.com/a/70354583
|
||||
try {
|
||||
CookieManager.getInstance();
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// WebView installed but not enabled
|
||||
return SystemWebViewStatus.DISABLED;
|
||||
} catch (Exception e) {
|
||||
return SystemWebViewStatus.DISABLED;
|
||||
}
|
||||
|
||||
return SystemWebViewStatus.FULL;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
|
|||
|
|
@ -131,10 +131,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">ကဳဗုဒ် %1$s စုတ်လဝ်တုဲ</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">ဇိုတ် ကဳဗုဒ်တုဲ</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">ပံက် ပလေဝ်ဒါန်</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">ပံက် တၚ်ချိၚ်ခယျဂမၠိုၚ်</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Disable suggestions</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">တၚ်ချိၚ်ခယျဂမၠိုၚ်ဟေၚ်</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">တၚ်ချၚ်ခယျ ကေုာံ တၚ်ပလေဝ်ဂမၠိုၚ်</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">တၚ်ချိၚ်ခယျနကဵု တၚ်ပလေဝ်အဝ်တဵု</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">အဘိဓာန်</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_keyboards">
|
||||
<item quantity="one">Keyboard</item>
|
||||
<item quantity="other">Keyboards</item>
|
||||
<item quantity="one">Keyboard</item>
|
||||
<item quantity="other">Keyboards</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_other_input_methods">
|
||||
<item quantity="one">Other Input Method</item>
|
||||
<item quantity="other">Other Input Methods</item>
|
||||
<item quantity="one">Other Input Method</item>
|
||||
<item quantity="other">Other Input Methods</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Title for list -->
|
||||
|
|
@ -127,10 +127,34 @@
|
|||
<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: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">
|
||||
<!-- Context: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">
|
||||
Error in keyboard %1$s:%2$s for %3$s language.</string>
|
||||
|
||||
<!-- Context: Alert Dialog title: System WebView not installed or enabled -->
|
||||
<string name="alert_dialog_title_webview_missing">
|
||||
Fatal Error: WebView Missing</string>
|
||||
|
||||
<!-- Context: Alert Dialog body: System WebView not installed -->
|
||||
<string name="body_install_webview" comment="Notification when WebView not installed">
|
||||
Keyman requires WebView to be installed."</string>
|
||||
|
||||
<!-- ContexT: Alert Dialog body: Switch to another keyboard -->
|
||||
<string name="body_switch_keyboard" comment="Direct user to select another keyboard">
|
||||
Keyman requires WebView to be installed and enabled.\n\n
|
||||
Switch to a different keyboard.
|
||||
</string>
|
||||
|
||||
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
|
||||
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">
|
||||
Keyman requires WebView to be enabled."</string>
|
||||
|
||||
<!-- Context Button label: Install WebView -->
|
||||
<string name="label_install_webview" comment="Button label to install webview">Install WebView</string>
|
||||
|
||||
<!-- Context Button label: Enable WebView -->
|
||||
<string name="label_enable_webview" comment="Button label to enable webview">Enable WebView</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>
|
||||
|
||||
|
|
@ -184,6 +208,9 @@
|
|||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Failed to access server!</string>
|
||||
|
||||
<!-- Context: Update-check failure notification -->
|
||||
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager disabled - cannot check for updates</string>
|
||||
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"All resources are up to date!"</string>
|
||||
|
||||
|
|
@ -229,8 +256,8 @@
|
|||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Dictionary</string>
|
||||
<plurals name="model_count">
|
||||
<item quantity="one">Dictionary</item>
|
||||
<item quantity="other">Dictionaries</item>
|
||||
<item quantity="one">Dictionary</item>
|
||||
<item quantity="other">Dictionaries</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Language Settings menu substring - Check for available dictionary -->
|
||||
|
|
@ -251,8 +278,8 @@
|
|||
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<plurals name="keyboard_count">
|
||||
<item quantity="one">(%1$d keyboard)</item>
|
||||
<item quantity="other">(%1$d keyboards)</item>
|
||||
<item quantity="one">(%1$d keyboard)</item>
|
||||
<item quantity="other">(%1$d keyboards)</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
|
|
@ -278,4 +305,5 @@
|
|||
|
||||
<!-- Context: anywhere -->
|
||||
<string name="unable_to_open_browser" comment="Notification when a browser activity cannot be launched">Unable to launch web browser</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="AppTheme.NoTitleDialog" parent="AppTheme.Light.Dialog">
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
|
|
|
|||
|
|
@ -34,5 +34,4 @@ The following code illustrates the use of `getLongpressDelay()`:
|
|||
Keyman Engine for Android 18.0: New function.
|
||||
|
||||
## See also
|
||||
* [sendOptionsToKeyboard](sendOptionsToKeyboard)
|
||||
* [setLongpressDelay](setLongpressDelay)
|
||||
|
|
|
|||
|
|
@ -212,9 +212,6 @@ The KMManager is the core class which provides most of the methods and constants
|
|||
[`removeKeyboardEventListener()`](removeKeyboardEventListener)
|
||||
: removes the specified listener from the list of keyboard event listeners
|
||||
|
||||
[`sendOptionsToKeyboard()`](sendOptionsToKeyboard)
|
||||
: sends options like longpress delay to the KeymanWeb keyboard
|
||||
|
||||
[`setCanAddNewKeyboard()`](setCanAddNewKeyboard)
|
||||
: sets whether adding a new keyboard is allowed
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
title: KMManager.sendOptionsToKeyboard()
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The `sendOptionsToKeyboard()` method sends options like longpress delay to the KeymanWeb keyboard.
|
||||
|
||||
## Syntax
|
||||
|
||||
```java
|
||||
KMManager.sendOptionsToKeyboard()
|
||||
```
|
||||
|
||||
## Description
|
||||
Use this method to update options in the KeymanWeb keyboard.
|
||||
* Number of milliseconds to trigger a longpress gesture
|
||||
|
||||
This method requires a keyboard to be loaded for the values to take effect.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example: Using `sendOptionsToKeyboard()`
|
||||
|
||||
The following code illustrates the use of `sendOptionsToKeyboard()`:
|
||||
```java
|
||||
int currentDelayTimeMS = 250;
|
||||
|
||||
// Store currentDelayTimeMS
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
|
||||
// Apply the keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
```
|
||||
|
||||
## History
|
||||
Keyman Engine for Android 18.0: New function.
|
||||
|
||||
## See also
|
||||
* [getLongpressDelay](getLongpressDelay)
|
||||
* [setLongpressDelay](setLongpressDelay)
|
||||
|
|
@ -4,7 +4,7 @@ title: KMManager.setLongpressDelay()
|
|||
|
||||
## Summary
|
||||
|
||||
The `setLongpressDelay()` method sets the number of milliseconds to trigger a longpress gesture as a stored preference.
|
||||
The `setLongpressDelay()` method sets the number of milliseconds to trigger a longpress gesture, applies to the keyboard, and stores preference.
|
||||
|
||||
## Syntax
|
||||
|
||||
|
|
@ -27,11 +27,8 @@ The following code illustrates the use of `setLongpressDelay()`:
|
|||
```java
|
||||
int currentDelayTimeMS = 300;
|
||||
|
||||
// Store currentDelayTimeMS
|
||||
// Store currentDelayTimeMS and applies
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
|
||||
// Apply the keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
```
|
||||
|
||||
## History
|
||||
|
|
@ -39,4 +36,3 @@ Keyman Engine for Android 18.0: New function.
|
|||
|
||||
## See also
|
||||
* [getLongpressDelay](getLongpressDelay)
|
||||
* [sendOptionsToKeyboard](sendOptionsToKeyboard)
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ export class CopierMessages {
|
|||
o.e ?? 'unknown error'
|
||||
);
|
||||
|
||||
static INFO_CopyingProject = SevInfo | 0x0002;
|
||||
static Info_CopyingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_CopyingProject,
|
||||
`Copying project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
// unused 0x0002 (was INFO_CopyingProject, now in InfrastructureMessages)
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.Generator;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | Namespace;
|
||||
const SevError = CompilerErrorSeverity.Error | Namespace;
|
||||
|
|
@ -23,11 +23,7 @@ export class GeneratorMessages {
|
|||
o.e ?? 'unknown error',
|
||||
);
|
||||
|
||||
static INFO_GeneratingProject = SevInfo | 0x0002;
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
// unused 0x0002 (was INFO_GeneratingProject, now in InfrastructureMessages)
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
|
|
|
|||
|
|
@ -79,6 +79,18 @@ async function doCopy(callbacks: NodeCompilerCallbacks, sources: string | string
|
|||
return false;
|
||||
}
|
||||
|
||||
const dest = options.outPath;
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_CopyingProject({source, dest}));
|
||||
const result = runCopier(callbacks, source, options);
|
||||
if(result) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectCopiedSuccessfully({source, dest}));
|
||||
} else {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotCopiedSuccessfully({source, dest}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function runCopier(callbacks: NodeCompilerCallbacks, source: string, options: CopierOptions) {
|
||||
const copier = new KeymanProjectCopier();
|
||||
try {
|
||||
if(!await copier.init(callbacks, options)) {
|
||||
|
|
|
|||
|
|
@ -113,9 +113,16 @@ async function generate(generator: KeymanCompiler, ids: string | string[], comma
|
|||
}
|
||||
|
||||
const options = commanderOptionsToGeneratorOptions(id, commanderOptions);
|
||||
|
||||
const outPath = options.outPath;
|
||||
const callbacks = new NodeCompilerCallbacks(options);
|
||||
if(!await doGenerate(callbacks, generator, options)) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_GeneratingProject({id, outPath}));
|
||||
|
||||
const result = await doGenerate(callbacks, generator, options);
|
||||
|
||||
if(result) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectGeneratedSuccessfully({id}));
|
||||
} else {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotGeneratedSuccessfully({id}));
|
||||
return await exitProcess(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,5 +158,48 @@ export class InfrastructureMessages {
|
|||
this.ERROR_CopyRequiresOutPath,
|
||||
`The copy command requires the --out-path, -o parameter`
|
||||
);
|
||||
}
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_CopyingProject = SevInfo | 0x0023;
|
||||
static Info_CopyingProject = (o:{source:string,dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_CopyingProject,
|
||||
`Copying project '${def(o.source)}' to '${def(o.dest)}'`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectCopiedSuccessfully = SevInfo | 0x0024;
|
||||
static Info_ProjectCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_ProjectCopiedSuccessfully,
|
||||
`'${def(o.source)}' copied to '${def(o.dest)}' successfully.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectNotCopiedSuccessfully = SevInfo | 0x0025;
|
||||
static Info_ProjectNotCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_ProjectNotCopiedSuccessfully,
|
||||
`Failed to copy '${def(o.source)}' to '${def(o.dest)}'.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_GeneratingProject = SevInfo | 0x0026;
|
||||
static Info_GeneratingProject = (o:{id:string, outPath: string}) => ({filename:o.id, ...m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating new project '${def(o.id)}' in '${def(o.outPath)}'.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectGeneratedSuccessfully = SevInfo | 0x0027;
|
||||
static Info_ProjectGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
|
||||
this.INFO_ProjectGeneratedSuccessfully,
|
||||
`New project '${def(o.id)}' generated successfully.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectNotGeneratedSuccessfully = SevInfo | 0x0028;
|
||||
static Info_ProjectNotGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
|
||||
this.INFO_ProjectNotGeneratedSuccessfully,
|
||||
`Failed to generate new project '${def(o.id)}'.`,
|
||||
)});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -242,12 +242,18 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
messageSpecialColor(event: CompilerEvent) {
|
||||
switch(event.code) {
|
||||
case InfrastructureMessages.INFO_BuildingFile:
|
||||
case InfrastructureMessages.INFO_CopyingProject:
|
||||
case InfrastructureMessages.INFO_GeneratingProject:
|
||||
return color.whiteBright;
|
||||
case InfrastructureMessages.INFO_FileNotBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotCopiedSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotGeneratedSuccessfully:
|
||||
return color.red;
|
||||
case InfrastructureMessages.INFO_FileBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectCopiedSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectGeneratedSuccessfully:
|
||||
return color.green;
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"valuesFormatting": "parseText"
|
||||
},
|
||||
{
|
||||
// start: gdbserver 10.3.0.58:2345 /media/sf_Develop/keyman/keyman/linux/build/x86_64/debug/tests/ibus-keyman-tests
|
||||
// start: linux/ibus-keyman/tests/scripts/run-tests.sh --remote-debug --no-surrounding-text --no-wayland -- k_001___basic_input_unicodei
|
||||
// then attach debugger in vscode
|
||||
"type": "gdb",
|
||||
"request": "attach",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ title: What's New in Keyman 18.0 for iPhone and iPad
|
|||
---
|
||||
Here are some significant changes to Keyman for iPhone and iPad 18.0:
|
||||
|
||||
* Minimum supported version of iOS is 13.0.
|
||||
* Minimum supported version of iOS is 15.6.
|
||||
* Predictive text and on screen keyboard startup performance improvements (#11784, #11264, #11265)
|
||||
* Keyboard size can now be adjusted to your preference (#12571)
|
||||
|
|
@ -1711,7 +1711,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SystemKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1757,7 +1757,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SystemKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1805,7 +1805,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1849,7 +1849,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1900,7 +1900,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngine/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1955,7 +1955,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngine/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1979,7 +1979,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F27FCAF6157FD95E00FBBA20 /* Keyman-lib.xcconfig */;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
STRIP_STYLE = all;
|
||||
|
|
@ -1992,7 +1992,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F27FCAF6157FD95E00FBBA20 /* Keyman-lib.xcconfig */;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
};
|
||||
|
|
@ -2021,7 +2021,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2058,7 +2058,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2110,7 +2110,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
|
|
@ -2151,7 +2151,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngine/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2175,7 +2175,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F27FCAF6157FD95E00FBBA20 /* Keyman-lib.xcconfig */;
|
||||
buildSettings = {
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
};
|
||||
|
|
@ -2194,7 +2194,7 @@
|
|||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 3YE4W86L3G;
|
||||
ENABLE_BITCODE = NO;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2244,7 +2244,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SystemKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2292,7 +2292,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2333,7 +2333,7 @@
|
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2385,7 +2385,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NO_SENTRY";
|
||||
|
|
@ -2428,7 +2428,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
|
@ -2449,7 +2449,7 @@
|
|||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 3YE4W86L3G;
|
||||
ENABLE_BITCODE = NO;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -2482,7 +2482,7 @@
|
|||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 3YE4W86L3G;
|
||||
ENABLE_BITCODE = NO;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ import UIKit
|
|||
extension Colors {
|
||||
public static var systemBackground: UIColor {
|
||||
get {
|
||||
if #available(iOS 13.0, *) {
|
||||
return UIColor.systemBackground
|
||||
} else {
|
||||
return UIColor.white
|
||||
}
|
||||
return UIColor.systemBackground
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,13 +208,6 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
edgesForExtendedLayout = []
|
||||
|
||||
rightNavigationMode = .next
|
||||
|
||||
if #available(*, iOS 13.4) {
|
||||
// No icon issues here.
|
||||
} else {
|
||||
tabVC.tabBar.items![0].titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -16)
|
||||
tabVC.tabBar.items![1].titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -16)
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only one language in the package, hide the language picker.
|
||||
|
|
@ -357,9 +350,7 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
self.pickingCompletionHandler(selectedResources.map { $0.typedFullID })
|
||||
|
||||
// Prevent swipe dismissal.
|
||||
if #available(iOS 13.0, *) {
|
||||
self.isModalInPresentation = true
|
||||
}
|
||||
self.isModalInPresentation = true
|
||||
|
||||
// No more selection-manipulation allowed.
|
||||
// This matters when there's no welcome page available.
|
||||
|
|
@ -376,9 +367,7 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
// First, show the package's welcome - if it exists.
|
||||
if let welcomeVC = PackageWebViewController(for: package, page: .welcome) {
|
||||
// Prevent swipe dismissal.
|
||||
if #available(iOS 13.0, *) {
|
||||
welcomeVC.isModalInPresentation = true
|
||||
}
|
||||
welcomeVC.isModalInPresentation = true
|
||||
|
||||
let subNavVC = UINavigationController(rootViewController: welcomeVC)
|
||||
_ = subNavVC.view
|
||||
|
|
@ -502,11 +491,7 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
|
|||
|
||||
private func setCellStyle(_ cell: UITableViewCell, style: CellStyle) {
|
||||
var textColor: UIColor
|
||||
if #available(*, iOS 13.0) {
|
||||
textColor = .label
|
||||
} else {
|
||||
textColor = .black
|
||||
}
|
||||
textColor = .label
|
||||
|
||||
switch style {
|
||||
case .none:
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
|
|||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
public func destroy() {
|
||||
// In app-extension mode, there are scenarios in which this class does not properly
|
||||
// deallocate! We need to help that process along. In particular, doing this allows
|
||||
// us to guarantee that the WebView is allowed to be GC'd, even when Apple fails to GC
|
||||
// this (`CustomInputView`) instance - which actually happens. (Refer to #12216.)
|
||||
keymanWeb.removeFromParent()
|
||||
keymanWeb.destroy()
|
||||
keymanWeb = nil
|
||||
}
|
||||
|
||||
public var enableInputClicksWhenVisible: Bool {
|
||||
get {
|
||||
// Implemented as noted by https://developer.apple.com/documentation/uikit/uidevice/1620050-playinputclick.
|
||||
|
|
@ -98,37 +108,37 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
|
|||
*/
|
||||
func keyboardHeightChanged() {
|
||||
os_log("CustomInputView keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .info)
|
||||
|
||||
|
||||
// deactivate constraints for both orientations (though one should already be inactive)
|
||||
landscapeConstraint?.isActive = false
|
||||
portraitConstraint?.isActive = false
|
||||
|
||||
|
||||
// rebuild both portrait and landscape constraints
|
||||
self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight)
|
||||
|
||||
|
||||
// activate constraints for the current orientation
|
||||
if InputViewController.isPortrait {
|
||||
portraitConstraint?.isActive = true
|
||||
} else {
|
||||
landscapeConstraint?.isActive = true
|
||||
}
|
||||
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
|
||||
private func buildKeyboardHeightConstraints(bannerHeight: CGFloat) {
|
||||
os_log("CustomInputView buildKeyboardHeightConstraints", log: KeymanEngineLogger.ui, type: .info)
|
||||
let innerView = keymanWeb.view!
|
||||
|
||||
|
||||
// Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard.
|
||||
let portraitHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: true)!)
|
||||
portraitHeightConstraint.identifier = "Height constraint for portrait mode"
|
||||
portraitHeightConstraint.priority = .defaultHigh
|
||||
|
||||
|
||||
let landscapeHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: false)!)
|
||||
landscapeHeightConstraint.identifier = "Height constraint for landscape mode"
|
||||
landscapeHeightConstraint.priority = .defaultHigh
|
||||
|
||||
|
||||
portraitConstraint = portraitHeightConstraint
|
||||
landscapeConstraint = landscapeHeightConstraint
|
||||
// .isActive will be set according to the current portrait/landscape perspective.
|
||||
|
|
@ -166,6 +176,8 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
var portraitConstraint: NSLayoutConstraint?
|
||||
var landscapeConstraint: NSLayoutConstraint?
|
||||
|
||||
var outerWidthConstraint: NSLayoutConstraint?
|
||||
|
||||
private var keymanWeb: KeymanWebViewController
|
||||
private var swallowBackspaceTextChange: Bool = false
|
||||
|
||||
|
|
@ -206,7 +218,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
keymanWeb = KeymanWebViewController(storage: Storage.active)
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
|
||||
var message = self.hasFullAccess ? "hasFullAccess: true" : "hasFullAccess: false"
|
||||
let message = self.hasFullAccess ? "hasFullAccess: true" : "hasFullAccess: false"
|
||||
os_log("%{public}s", log: KeymanEngineLogger.settings, type: .default, message)
|
||||
SentryManager.breadcrumb(message)
|
||||
|
||||
|
|
@ -217,6 +229,12 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
inputView?.removeFromSuperview()
|
||||
(inputView as? CustomInputView)?.destroy()
|
||||
inputView = nil
|
||||
}
|
||||
|
||||
open override func updateViewConstraints() {
|
||||
resetKeyboardState()
|
||||
|
||||
|
|
@ -301,6 +319,16 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
open override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
if outerWidthConstraint != nil {
|
||||
outerWidthConstraint?.isActive = false
|
||||
self.inputView?.removeConstraint(self.outerWidthConstraint!)
|
||||
outerWidthConstraint = nil
|
||||
}
|
||||
}
|
||||
|
||||
open override func textDidChange(_ textInput: UITextInput?) {
|
||||
// Swallows self-triggered calls from emptying the context due to keyboard rules
|
||||
if self.swallowBackspaceTextChange && textDocumentProxy.documentContextBeforeInput == nil {
|
||||
|
|
@ -368,7 +396,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func deleteSelection() -> Bool {
|
||||
if let selected = textDocumentProxy.selectedText, selected.count > 0 {
|
||||
/*
|
||||
|
|
@ -381,23 +409,23 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
/*
|
||||
We have a problem to resolve here: we cannot simply delete the selection
|
||||
with either .deleteBackward() or .insertText(""):
|
||||
|
||||
|
||||
- If there is selected text immediately following a space (U+0020),
|
||||
.deleteBackward() will delete that space IN ADDITION to the selected text.
|
||||
- Unlike .insertText("-any-string-here"), .insertText("") does nothing;
|
||||
it does not replace the selection with the new string.
|
||||
|
||||
|
||||
Our policy (#9073) on handling the backspace key when there is a
|
||||
text selection is to just delete the selection. We have to override
|
||||
the special case of space being deleted by .deleteBackward() ourselves.
|
||||
Additionally, the internal Web engine cannot anticipate the special
|
||||
case and requires precise and consistent backspace handling in line
|
||||
with our policy in order to keep the context on both sides synchronized.
|
||||
|
||||
|
||||
As .insertText() does not delete the selection if the string to
|
||||
be inserted is empty, we insert something that won't combine,
|
||||
like a ZWNJ, and then delete it.
|
||||
|
||||
|
||||
iOS does not allow users to select text in a way that splits
|
||||
character clusters. This implies that it's impossible for an
|
||||
inserted ZWNJ to combine with existing context, making this
|
||||
|
|
@ -405,14 +433,14 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
*/
|
||||
textDocumentProxy.insertText("\u{200c}")
|
||||
textDocumentProxy.deleteBackward()
|
||||
|
||||
|
||||
let afterManipulation = textDocumentProxy.documentContextBeforeInput ?? ""
|
||||
|
||||
|
||||
// And now to finish our 'canary' check.
|
||||
if beforeManipulation != afterManipulation {
|
||||
os_log(.error, log: KeymanEngineLogger.engine, "Could not cleanly execute backspace for selected text")
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
@ -527,11 +555,15 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
}
|
||||
|
||||
// These require the view to appear - parent and our relationship with it must exist!
|
||||
// ... wait, is THIS possibly a leak source?
|
||||
private func setOuterConstraints() {
|
||||
var baseWidthConstraint: NSLayoutConstraint
|
||||
baseWidthConstraint = self.inputView!.widthAnchor.constraint(equalTo: parent!.view.safeAreaLayoutGuide.widthAnchor)
|
||||
baseWidthConstraint.priority = UILayoutPriority(rawValue: 999)
|
||||
baseWidthConstraint.isActive = true
|
||||
guard outerWidthConstraint == nil else {
|
||||
outerWidthConstraint!.isActive = true
|
||||
return
|
||||
}
|
||||
outerWidthConstraint = self.inputView!.widthAnchor.constraint(equalTo: parent!.view.safeAreaLayoutGuide.widthAnchor)
|
||||
outerWidthConstraint!.priority = UILayoutPriority(rawValue: 999)
|
||||
outerWidthConstraint!.isActive = true
|
||||
}
|
||||
|
||||
public var kmwHeight: CGFloat {
|
||||
|
|
@ -560,7 +592,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
customInputView.keyboardHeightChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func fixLayout() {
|
||||
view.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
|
|
@ -589,7 +621,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
}
|
||||
|
||||
// KeymanWebViewController maintenance methods
|
||||
func reload() {
|
||||
public func reload() {
|
||||
keymanWeb.reloadKeyboard()
|
||||
}
|
||||
|
||||
|
|
@ -624,10 +656,18 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
keymanWeb.showHelpBubble()
|
||||
}
|
||||
|
||||
func dismissHelpBubble() {
|
||||
keymanWeb.dismissHelpBubble()
|
||||
}
|
||||
|
||||
func showHelpBubble(afterDelay delay: TimeInterval) {
|
||||
keymanWeb.showHelpBubble(afterDelay: delay)
|
||||
}
|
||||
|
||||
internal func enforceKeyboardSize() {
|
||||
keymanWeb.resizeKeyboard()
|
||||
}
|
||||
|
||||
func clearText() {
|
||||
setContextState(text: nil, range: NSRange(location: 0, length: 0))
|
||||
keymanWeb.resetContext()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe
|
|||
private var tableView: UITableView?
|
||||
private let closeButtonTitle: String?
|
||||
|
||||
private var _inputViewController: InputViewController?
|
||||
// Is populated during init from a weak ref by KeymanWebViewController,
|
||||
// and instances of this class are owned by the same.
|
||||
private weak var _inputViewController: InputViewController?
|
||||
override var inputViewController: InputViewController? {
|
||||
return _inputViewController
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,14 +72,6 @@ class KeymanWebViewController: UIViewController {
|
|||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
_ = view
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: UIApplication.willEnterForegroundNotification,
|
||||
object: nil,
|
||||
queue: OperationQueue.main
|
||||
) { _ in
|
||||
self.reloadKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
|
@ -154,19 +146,12 @@ class KeymanWebViewController: UIViewController {
|
|||
|
||||
view = webView
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow),
|
||||
name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide),
|
||||
name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
|
||||
reloadKeyboard()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if #available(iOSApplicationExtension 14.0, *) {
|
||||
self.userContentController.removeAllScriptMessageHandlers()
|
||||
}
|
||||
self.userContentController.removeAllScriptMessageHandlers()
|
||||
self.userContentController.add(self, name: keymanWebViewName)
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +171,18 @@ class KeymanWebViewController: UIViewController {
|
|||
|
||||
// MARK: - JavaScript functions
|
||||
extension KeymanWebViewController {
|
||||
// In app-extension mode, there are scenarios in which this class does not properly
|
||||
// deallocate! We need to help that process along.
|
||||
func destroy() {
|
||||
// Message handlers can maintain references.
|
||||
if #available(iOSApplicationExtension 14.0, *) {
|
||||
self.userContentController.removeAllScriptMessageHandlers()
|
||||
} else {
|
||||
self.userContentController.removeScriptMessageHandler(forName: keymanWebViewName)
|
||||
}
|
||||
view = nil
|
||||
}
|
||||
|
||||
func languageMenuPosition(_ completion: @escaping (CGRect) -> Void) {
|
||||
webView!.evaluateJavaScript("langMenuPos();") { result, _ in
|
||||
guard let result = result as? String, !result.isEmpty else {
|
||||
|
|
@ -990,18 +987,3 @@ extension KeymanWebViewController {
|
|||
return keyboardMenuView != nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Keyboard Notifications
|
||||
extension KeymanWebViewController {
|
||||
@objc func keyboardWillShow(_ notification: Notification) {
|
||||
resizeKeyboard()
|
||||
|
||||
if Manager.shared.isKeymanHelpOn {
|
||||
showHelpBubble(afterDelay: 1.5)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func keyboardWillHide(_ notification: Notification) {
|
||||
dismissHelpBubble()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,14 +355,8 @@ class KeyboardHeightViewController: UIViewController {
|
|||
var arrowSymbolImage: UIImage? = nil
|
||||
keyboardResizer.isUserInteractionEnabled = true
|
||||
|
||||
// if using iOS earlier than 13.0, use bitmap image of arrow and non-dynamic gray
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
arrowSymbolImage = UIImage(systemName: "arrow.up.and.down.square.fill")
|
||||
keyboardResizer.backgroundColor = UIColor.systemGray3 // adapts for light/dark mode
|
||||
} else {
|
||||
arrowSymbolImage = UIImage(named:"arrow.up.and.down", in:engineBundle, compatibleWith:nil)
|
||||
keyboardResizer.backgroundColor = UIColor.systemGray
|
||||
}
|
||||
arrowSymbolImage = UIImage(systemName: "arrow.up.and.down.square.fill")
|
||||
keyboardResizer.backgroundColor = UIColor.systemGray3 // adapts for light/dark mode
|
||||
keyboardResizer.image = arrowSymbolImage
|
||||
keyboardResizer.layer.cornerRadius = 5
|
||||
contentView.addSubview(keyboardResizer)
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
set(value) {
|
||||
// The system keyboard will set itself here when its `viewDidAppear` triggers.
|
||||
_inputViewController = value
|
||||
}
|
||||
}
|
||||
|
|
@ -239,10 +240,35 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
SentryManager.capture(error, message:message)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep these within our singleton Manager instance. As we never remove these,
|
||||
// setting them within something prone to replacement (like the keyboard when in
|
||||
// app-extension mode) can lead to memory leaks. See #12216.
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), //
|
||||
name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), //
|
||||
name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
|
||||
// We used to preload the old KeymanWebViewController, but now that it's embedded within the
|
||||
// InputViewController, that's not exactly viable.
|
||||
}
|
||||
|
||||
// MARK: - Keyboard Notifications
|
||||
// Do NOT place these anywhere within InputViewController or its children.
|
||||
// So far as we can tell, the OS causes the app extension to sometimes throw
|
||||
// instances away, and maintaining references via notification to them can
|
||||
// cause memory leaks. Refer to #12216.
|
||||
@objc func keyboardWillShow(_ notification: Notification) {
|
||||
inputViewController?.enforceKeyboardSize()
|
||||
|
||||
if Manager.shared.isKeymanHelpOn {
|
||||
inputViewController?.showHelpBubble(afterDelay: 1.5)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func keyboardWillHide(_ notification: Notification) {
|
||||
inputViewController?.dismissHelpBubble()
|
||||
}
|
||||
|
||||
// MARK: - Keyboard management
|
||||
|
||||
|
|
|
|||
|
|
@ -43,15 +43,13 @@ class PackageWebViewController: UIViewController, WKNavigationDelegate {
|
|||
let prefs = WKPreferences()
|
||||
prefs.javaScriptEnabled = true
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
/**
|
||||
In iPadOS 16 and above WKWebView defaults to lying about its user-agent,
|
||||
telling the web server that it is a mac. We can avoid this with .mobile:
|
||||
*/
|
||||
let pref = WKWebpagePreferences.init()
|
||||
pref.preferredContentMode = .mobile
|
||||
config.defaultWebpagePreferences = pref
|
||||
}
|
||||
/**
|
||||
In iPadOS 16 and above WKWebView defaults to lying about its user-agent,
|
||||
telling the web server that it is a mac. We can avoid this with .mobile:
|
||||
*/
|
||||
let pref = WKWebpagePreferences.init()
|
||||
pref.preferredContentMode = .mobile
|
||||
config.defaultWebpagePreferences = pref
|
||||
|
||||
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
||||
let metaViewportInjection = """
|
||||
|
|
|
|||
|
|
@ -104,15 +104,13 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
|
||||
let config = WKWebViewConfiguration()
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
/**
|
||||
In iPadOS 16 and above WKWebView defaults to lying about its user-agent,
|
||||
telling the web server that it is a mac. We can avoid this with .mobile:
|
||||
*/
|
||||
let pref = WKWebpagePreferences.init()
|
||||
pref.preferredContentMode = .mobile
|
||||
config.defaultWebpagePreferences = pref
|
||||
}
|
||||
/**
|
||||
In iPadOS 16 and above WKWebView defaults to lying about its user-agent,
|
||||
telling the web server that it is a mac. We can avoid this with .mobile:
|
||||
*/
|
||||
let pref = WKWebpagePreferences.init()
|
||||
pref.preferredContentMode = .mobile
|
||||
config.defaultWebpagePreferences = pref
|
||||
|
||||
let webView = WKWebView.init(frame: CGRect.zero, configuration: config)
|
||||
webView.navigationDelegate = self
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ PRODUCT_NAME = $(TARGET_NAME)
|
|||
GCC_PRECOMPILE_PREFIX_HEADER = YES
|
||||
GCC_PREFIX_HEADER = KME-iphoneos/KME-iphoneos-Prefix.pch
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.0
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ GCC_PREFIX_HEADER = KME-iphonesimulator/KME-iphonesimulator-Prefix.pch
|
|||
ARCHS = i386
|
||||
VALID_ARCHS = i386
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.0
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ PRODUCT_NAME = $(TARGET_NAME)
|
|||
GCC_PRECOMPILE_PREFIX_HEADER = YES
|
||||
GCC_PREFIX_HEADER = KeymanEngine/KeymanEngine-Prefix.pch
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.0
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow.up.and.down.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -226,6 +226,24 @@
|
|||
/* Text showing name of spacebar caption - language + keyboard */
|
||||
"menu-settings-spacebar-item-languageKeyboard" = "Jazyk a klávesnice";
|
||||
|
||||
/* Label for the "Adjust Keyboard Height" item on the main settings screen */
|
||||
"menu-settings-adjust-keyboard-height" = "Upravit výšku klávesnice";
|
||||
|
||||
/* Title for the "Adjust Keyboard Height" settings child screen */
|
||||
"adjust-keyboard-height-title" = "Upravit klávesnici";
|
||||
|
||||
/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */
|
||||
"button-label-reset-default-keyboard-height" = "Obnovit výchozí výšku klávesnice";
|
||||
|
||||
/* Instruction text to drag keyboard to resize */
|
||||
"keyboard-drag-instructions" = "Tažením šipky nastavíte výšku klávesnice.";
|
||||
|
||||
/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */
|
||||
"portrait-keyboard-rotate-instructions" = "Otočte zařízení pro nastavení na šířku.";
|
||||
|
||||
/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */
|
||||
"landscape-keyboard-rotate-instructions" = "Otočte zařízení pro nastavení na výšku.";
|
||||
|
||||
/* Short text for notification: download failure for keyboard */
|
||||
"notification-download-failure-keyboard" = "Stahování klávesnice se nezdařilo";
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,24 @@
|
|||
/* Text showing name of spacebar caption - language + keyboard */
|
||||
"menu-settings-spacebar-item-languageKeyboard" = "ဘာသာ ကေုာံ ကဳဗုဒ်";
|
||||
|
||||
/* Label for the "Adjust Keyboard Height" item on the main settings screen */
|
||||
"menu-settings-adjust-keyboard-height" = "ချိၚ်ဆသမၠုၚ်ကဳဗုဒ်ညိ";
|
||||
|
||||
/* Title for the "Adjust Keyboard Height" settings child screen */
|
||||
"adjust-keyboard-height-title" = "ချိၚ်ကဳဗုဒ်";
|
||||
|
||||
/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */
|
||||
"button-label-reset-default-keyboard-height" = "ကလေၚ်ကေတ် သမၠုၚ်ကဳဗုဒ် ပကတိ";
|
||||
|
||||
/* Instruction text to drag keyboard to resize */
|
||||
"keyboard-drag-instructions" = "သ္ဂောံချိၚ်သမၠုၚ်ကဳဗုဒ်ဂှ် ဓဇက်မလဴဂှ်ညိ";
|
||||
|
||||
/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */
|
||||
"portrait-keyboard-rotate-instructions" = "ကလေၚ်ကေတ်ဗီုပြၚ်ပကတိ";
|
||||
|
||||
/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */
|
||||
"landscape-keyboard-rotate-instructions" = "သ္ဂောံစွံလ္ပါ်ပဇူဂှ် ဗဂေတ်ကဵုစက်ညိ";
|
||||
|
||||
/* Short text for notification: download failure for keyboard */
|
||||
"notification-download-failure-keyboard" = "ဂြဲဖျေံကဳဗုဒ်ဟွံအံၚ်ဇၞး";
|
||||
|
||||
|
|
|
|||
|
|
@ -1131,7 +1131,7 @@
|
|||
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
|
||||
INFOPLIST_FILE = Keyman/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1166,7 +1166,7 @@
|
|||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
|
||||
INFOPLIST_FILE = Keyman/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1232,7 +1232,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
|
|
@ -1284,7 +1284,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1362,7 +1362,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1431,7 +1431,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
|
|
@ -1456,7 +1456,7 @@
|
|||
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
|
||||
INFOPLIST_FILE = Keyman/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1500,7 +1500,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -67,9 +67,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
// In iOS 15, navigation bars become transparent by default when the edge
|
||||
// of the scrollable content aligns with the edge of the navigation bar.
|
||||
// Force the appearance back to the pre-iOS 15 default behavior.
|
||||
if #available(iOS 15, *) {
|
||||
UINavigationBar.appearance().scrollEdgeAppearance = UINavigationBarAppearance()
|
||||
}
|
||||
UINavigationBar.appearance().scrollEdgeAppearance = UINavigationBarAppearance()
|
||||
|
||||
UniversalLinks.externalLinkLauncher = { url in
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,13 +54,9 @@ class WebBrowserViewController: UIViewController, WKNavigationDelegate, UIAlertV
|
|||
|
||||
webView.navigationDelegate = self
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// Dark mode settings must be applied through this new property,
|
||||
// its class, and others like it.
|
||||
navBar.standardAppearance.configureWithOpaqueBackground()
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
// Dark mode settings must be applied through this new property,
|
||||
// its class, and others like it.
|
||||
navBar.standardAppearance.configureWithOpaqueBackground()
|
||||
|
||||
// Setup NavigationBar
|
||||
navbarBackground = KMNavigationBarBackgroundView()
|
||||
|
|
@ -119,12 +115,6 @@ class WebBrowserViewController: UIViewController, WKNavigationDelegate, UIAlertV
|
|||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// 13.0 auto-adjusts the top to avoid the status bar.
|
||||
} else {
|
||||
navBarTopConstraint.constant = AppDelegate.statusBarHeight()
|
||||
}
|
||||
}
|
||||
|
||||
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,14 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
|
|||
forName: Notifications.keyboardRemoved,
|
||||
observer: self,
|
||||
function: MainViewController.keyboardRemoved)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: UIApplication.willEnterForegroundNotification,
|
||||
object: nil,
|
||||
queue: OperationQueue.main
|
||||
) { _ in
|
||||
Manager.shared.inputViewController.reload()
|
||||
}
|
||||
|
||||
// Unfortunately, it's the main app with the file definitions.
|
||||
// We have to gerry-rig this so that the framework-based SettingsViewController
|
||||
|
|
@ -278,13 +286,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
|
|||
|
||||
// Setup NavigationBar
|
||||
if let navbar = navigationController?.navigationBar {
|
||||
if #available(iOS 13.0, *) {
|
||||
// Dark mode settings must be applied through this new property,
|
||||
// its class, and others like it.
|
||||
navbar.standardAppearance.configureWithOpaqueBackground()
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
navbar.standardAppearance.configureWithOpaqueBackground()
|
||||
navbarBackground = KMNavigationBarBackgroundView()
|
||||
navbarBackground.addToNavbar(navbar)
|
||||
navbarBackground.setOrientation(UIApplication.shared.statusBarOrientation)
|
||||
|
|
@ -695,12 +697,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
|
|||
|
||||
let lframe = CGRect(x: 0, y: bframe.origin.y - 1, width: sframe.size.width, height: 1)
|
||||
let borderLine = UIView(frame: lframe)
|
||||
if #available(iOS 13.0, *) {
|
||||
borderLine.backgroundColor = UIColor.separator
|
||||
} else {
|
||||
let c: CGFloat = 230.0 / 255.0
|
||||
borderLine.backgroundColor = UIColor(red: c, green: c, blue: c, alpha: 1.0)
|
||||
}
|
||||
borderLine.backgroundColor = UIColor.separator
|
||||
containerView.addSubview(borderLine)
|
||||
|
||||
let tframe = CGRect(x: 0, y: 0, width: bframe.size.width, height: 40)
|
||||
|
|
@ -709,11 +706,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
|
|||
textSizeTitle.backgroundColor = UIColor.clear
|
||||
textSizeTitle.text = String.init(format: NSLocalizedString("text-size-label", comment: ""), Int(textSize))
|
||||
textSizeTitle.textAlignment = .center
|
||||
if #available(iOS 13.0, *) {
|
||||
textSizeTitle.textColor = UIColor.secondaryLabel
|
||||
} else {
|
||||
textSizeTitle.textColor = UIColor.gray
|
||||
}
|
||||
textSizeTitle.textColor = UIColor.secondaryLabel
|
||||
textSizeTitle.font = textSizeTitle.font.withSize(14.0)
|
||||
containerView.addSubview(textSizeTitle)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,6 @@ class PackageBrowserViewController: UIDocumentPickerViewController, UIDocumentPi
|
|||
bundle: Bundle(for: Manager.self),
|
||||
comment: "")
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// Easily dismissable without the extra button.
|
||||
} else {
|
||||
self.navigationItem.setLeftBarButton(
|
||||
UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.doCancel)),
|
||||
animated: true)
|
||||
}
|
||||
self.modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,28 +81,26 @@ class KeyboardViewController: InputViewController {
|
|||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
guard let previousTraitCollection = previousTraitCollection else {return}
|
||||
if #available(iOS 13.0, *) {
|
||||
if previousTraitCollection.hasDifferentColorAppearance(comparedTo: traitCollection) {
|
||||
/* Ensure that the keyboard banner image transitions! Noting that the backing view isn't in the view hierarchy,
|
||||
* and that manipulating said hierarchy is key for triggering the appearance change
|
||||
* (see https://developer.apple.com/documentation/uikit/uiappearance)...
|
||||
*
|
||||
* We have to reload the banner's backing view to trigger the change. Thanks, Apple.
|
||||
* The only other alternative - creating a new instance of the view & its controller.
|
||||
*
|
||||
* With loadView, at least we can reuse the old instance, which would serve far better
|
||||
* for engine API calls if we decide to let the base InputViewController perform UIView
|
||||
* render functionality (as a KMEI offering) instead of having it only in our app.
|
||||
* That said, note https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview:
|
||||
*
|
||||
* > You should never call this method directly.
|
||||
*
|
||||
* Well... I wouldn't, if it weren't the only way to trigger this without requiring a new instance.
|
||||
* We should be fine since we never place the banner's backing view into the actual hierarchy.
|
||||
*/
|
||||
topBarImageSource.loadView()
|
||||
setupTopBarImage(size: view.frame.size)
|
||||
}
|
||||
if previousTraitCollection.hasDifferentColorAppearance(comparedTo: traitCollection) {
|
||||
/* Ensure that the keyboard banner image transitions! Noting that the backing view isn't in the view hierarchy,
|
||||
* and that manipulating said hierarchy is key for triggering the appearance change
|
||||
* (see https://developer.apple.com/documentation/uikit/uiappearance)...
|
||||
*
|
||||
* We have to reload the banner's backing view to trigger the change. Thanks, Apple.
|
||||
* The only other alternative - creating a new instance of the view & its controller.
|
||||
*
|
||||
* With loadView, at least we can reuse the old instance, which would serve far better
|
||||
* for engine API calls if we decide to let the base InputViewController perform UIView
|
||||
* render functionality (as a KMEI offering) instead of having it only in our app.
|
||||
* That said, note https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview:
|
||||
*
|
||||
* > You should never call this method directly.
|
||||
*
|
||||
* Well... I wouldn't, if it weren't the only way to trigger this without requiring a new instance.
|
||||
* We should be fine since we never place the banner's backing view into the actual hierarchy.
|
||||
*/
|
||||
topBarImageSource.loadView()
|
||||
setupTopBarImage(size: view.frame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
|
|
@ -342,7 +342,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -365,7 +365,7 @@
|
|||
"$(SRCROOT)/../../Carthage/Build/",
|
||||
);
|
||||
INFOPLIST_FILE = KMSample1/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -390,7 +390,7 @@
|
|||
"$(SRCROOT)/../../Carthage/Build/",
|
||||
);
|
||||
INFOPLIST_FILE = KMSample1/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
|
@ -467,7 +467,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
|
|
@ -518,7 +518,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -540,7 +540,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = KMSample2/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -566,7 +566,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = KMSample2/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -593,7 +593,7 @@
|
|||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -619,7 +619,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = CalibrationKbd/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -411,7 +411,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = CalibrationKbd/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -546,9 +546,9 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = KeyboardCalibrator/KeyboardCalibrator.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = KeyboardCalibrator/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -567,9 +567,9 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = KeyboardCalibrator/KeyboardCalibrator.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = KeyboardCalibrator/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = DefaultKeyboardHostTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -611,7 +611,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = DefaultKeyboardHostTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -632,6 +632,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = DefaultKeyboardHostUITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -652,6 +653,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = WHE552KZN5;
|
||||
INFOPLIST_FILE = DefaultKeyboardHostUITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
|
|||
|
|
@ -2,4 +2,12 @@
|
|||
title: What's New in Keyman 18.0 for Linux
|
||||
---
|
||||
|
||||
Here are some of the new features we have added to Keyman for Linux 18.0:
|
||||
Here are some significant changes to Keyman for Linux 18.0:
|
||||
|
||||
- Minimum supported Ubuntu version is now 22.04 Jammy LTS.
|
||||
- Other supported versions are Ubuntu 24.04 Noble LTS and
|
||||
Ubuntu 24.10 Oracular. Users that are running an older version of
|
||||
Ubuntu will have to continue using an older Keyman version.
|
||||
- Keyman no longer requires a patched version of ibus as Keyman now uses
|
||||
a system service to manage keystroke order (#11535).
|
||||
- Added support for simulation of AltGr (right Alt) with Ctrl+Alt (#11852)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ fi
|
|||
|
||||
if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null; then
|
||||
echo "# Please install Xvfb, Xephyr and metacity before running these tests!"
|
||||
echo "# sudo apt install xvfb xserver-xephyr metacity"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -24,3 +24,26 @@ To run a single test you pass the testname (as found in
|
|||
```bash
|
||||
scripts/run-tests.sh -- k_000___null_keyboard k_005___nul_with_initial_context
|
||||
```
|
||||
|
||||
## Debugging tests
|
||||
|
||||
This is most easily done by running the tests in a VM and then remote debugging
|
||||
into the VM. The VM should be the same Linux version as the host, and the
|
||||
Keyman repo should be shared with the VM through a shared folder.
|
||||
|
||||
Then you can start the `run-tests.sh` script on the VM with the
|
||||
`--remote-debug` option. This will start the background services and then
|
||||
start a GDB session waiting for the debugger to connect.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
linux/ibus-keyman/tests/scripts/run-tests.sh --remote-debug \
|
||||
--no-surrounding-text --no-wayland -- k_001___basic_input_unicodei
|
||||
```
|
||||
|
||||
On the host, you can then attach to the VM. See the
|
||||
`Attach to gdbserver (ibus-keyman integration tests)` configuration
|
||||
in `docs/settings/linux/launch.json` for a sample configuration in
|
||||
vscode. You'll have to adjust the IP address to match the VM which the
|
||||
`run-tests.sh` script will output.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ fi
|
|||
|
||||
if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null || ! which mutter > /dev/null; then
|
||||
echo "Please install Xvfb, Xephyr, metacity and mutter before running these tests!"
|
||||
echo "sudo apt install xvfb xserver-xephyr metacity mutter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ function help() {
|
|||
function run_tests() {
|
||||
# Output these lines to stderr - the first line on stdout has to be the TAP version number
|
||||
# which running ${TESTFILE} outputs
|
||||
echo "# NOTE: When the tests fail check /tmp/ibus-engine-keyman.log and /tmp/ibus-daemon.log!" >&2
|
||||
echo "# NOTE: When the tests fail check /tmp/ibus-engine-keyman.log, /tmp/ibus-daemon.log and /tmp/km-test-server.log!" >&2
|
||||
echo "" >&2
|
||||
|
||||
echo "# Starting tests..." >&2
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ fi
|
|||
|
||||
if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null || ! which mutter > /dev/null; then
|
||||
echo "Please install Xvfb, Xephyr, metacity and mutter before running these tests!"
|
||||
echo "sudo apt install xvfb xserver-xephyr metacity mutter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ function help() {
|
|||
echo " --help, -h, -? Display this help"
|
||||
echo " --verbose, -v Run tests verbosely"
|
||||
echo " --debug debug test logging output"
|
||||
echo " --remote-debug start gdbserver after setup but prior to running the tests"
|
||||
echo " -k passed to GLib testing framework"
|
||||
echo " --tap output in TAP format. Passed to GLib testing framework"
|
||||
echo " --surrounding-text run tests with surrounding texts enabled"
|
||||
|
|
@ -86,10 +88,14 @@ function run_tests() {
|
|||
source "$ENV_FILE"
|
||||
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS"
|
||||
|
||||
if [[ -n ${REMOTE_DEBUG:-} ]]; then
|
||||
echo "===> Now attach debugger to ${REMOTE_HOST}"
|
||||
fi
|
||||
|
||||
# Note: -k and --tap are consumed by the GLib testing framework
|
||||
#shellcheck disable=SC2068 # we want to split array elements!
|
||||
#shellcheck disable=SC2086
|
||||
"${G_TEST_BUILDDIR:-../../build/$(arch)/${CONFIG}/tests}/ibus-keyman-tests" ${ARG_K-} ${ARG_TAP-} \
|
||||
${REMOTE_DEBUG:-} "${G_TEST_BUILDDIR:-../../build/$(arch)/${CONFIG}/tests}/ibus-keyman-tests" ${ARG_K-} ${ARG_TAP-} \
|
||||
${ARG_VERBOSE-} ${ARG_DEBUG-} ${ARG_SURROUNDING_TEXT-} ${ARG_NO_SURROUNDING_TEXT-} \
|
||||
--directory "$TESTDIR" "${DISPLAY_SERVER}" ${TESTFILES[@]}
|
||||
echo "# Finished tests."
|
||||
|
|
@ -111,6 +117,7 @@ while (( $# )); do
|
|||
--no-x11) USE_X11=0;;
|
||||
--verbose|-v) ARG_VERBOSE=--verbose;;
|
||||
--debug) ARG_DEBUG=--debug-log;;
|
||||
--remote-debug) REMOTE_HOST="$(ip route get 8.8.8.8 | sed -E 's/.*src (\S+) .*/\1/;t;d'):2345" && REMOTE_DEBUG="gdbserver ${REMOTE_HOST}";;
|
||||
--) shift && break ;;
|
||||
*) echo "Error: Unexpected argument \"$1\". Exiting." ; exit 4 ;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ function _setup_ibus() {
|
|||
echo "if kill -9 ${PID}; then ibus restart || ibus start; fi # ibus-daemon" >> "${CLEANUP_FILE}"
|
||||
else
|
||||
# test run as part of the build
|
||||
echo "kill -9 ${PID} || true" >> "${CLEANUP_FILE}"
|
||||
echo "kill -9 ${PID} || true # ibus-daemon" >> "${CLEANUP_FILE}"
|
||||
fi
|
||||
echo "${PID} ibus-daemon" >> "${PID_FILE}"
|
||||
sleep 1s
|
||||
|
|
|
|||
|
|
@ -88,16 +88,6 @@ ibus_keyman_tests_fixture_tear_down(IBusKeymanTestsFixture *fixture, gconstpoint
|
|||
delete data;
|
||||
}
|
||||
|
||||
static void
|
||||
set_caps_lock_state(IBusKeymanTestsFixture *fixture, bool caps_lock_on) {
|
||||
set_capslock_indicator((guint32)caps_lock_on);
|
||||
}
|
||||
|
||||
static bool
|
||||
get_caps_lock_state(IBusKeymanTestsFixture *fixture) {
|
||||
return get_capslock_indicator();
|
||||
}
|
||||
|
||||
static void
|
||||
switch_keyboard(IBusKeymanTestsFixture *fixture, const gchar *keyboard) {
|
||||
ibus_bus_set_global_engine(fixture->bus, keyboard);
|
||||
|
|
@ -185,9 +175,9 @@ is_modifier(guint16 keycode) {
|
|||
}
|
||||
|
||||
static std::list<gdk_key_event>
|
||||
get_involved_keys(IBusKeymanTestsFixture *fixture, km::tests::key_event test_event) {
|
||||
get_involved_keys(km::tests::key_event test_event) {
|
||||
guint modifiers = 0;
|
||||
if (get_caps_lock_state(fixture)) {
|
||||
if (get_capslock_indicator()) {
|
||||
modifiers = IBUS_LOCK_MASK;
|
||||
}
|
||||
|
||||
|
|
@ -250,13 +240,13 @@ get_context_keys(std::u16string context) {
|
|||
}
|
||||
|
||||
static void
|
||||
press_keys(IBusKeymanTestsFixture *fixture, km::tests::KmxTestSource & test_source,km::tests::key_event key_event) {
|
||||
auto involved_keys = get_involved_keys(fixture, key_event);
|
||||
press_keys(IBusKeymanTestsFixture *fixture, km::tests::key_event key_event) {
|
||||
auto involved_keys = get_involved_keys(key_event);
|
||||
|
||||
// press and hold the individual modifier keys, finally the actual key
|
||||
std::stack<GdkEventKey> keys_to_release;
|
||||
for (auto key = involved_keys.begin(); key != involved_keys.end(); key++) {
|
||||
bool old_caps_lock = get_caps_lock_state(fixture);
|
||||
bool old_caps_lock = get_capslock_indicator();
|
||||
guint lock_modifier = 0;
|
||||
|
||||
// KEY_CAPSLOCK behaves a bit strange:
|
||||
|
|
@ -267,7 +257,7 @@ press_keys(IBusKeymanTestsFixture *fixture, km::tests::KmxTestSource & test_sour
|
|||
// - the LOCK_MASK modifier is set on both keypress and release
|
||||
// - capslock gets turned off on release
|
||||
if (key->keycode == KEY_CAPSLOCK && !old_caps_lock) {
|
||||
set_caps_lock_state(fixture, true);
|
||||
set_capslock_indicator((guint32)true);
|
||||
lock_modifier = IBUS_LOCK_MASK;
|
||||
}
|
||||
GdkEventKey keyEvent = {
|
||||
|
|
@ -289,7 +279,7 @@ press_keys(IBusKeymanTestsFixture *fixture, km::tests::KmxTestSource & test_sour
|
|||
keys_to_release.push(keyEvent);
|
||||
|
||||
if (key->keycode == KEY_CAPSLOCK && old_caps_lock) {
|
||||
set_caps_lock_state(fixture, false);
|
||||
set_capslock_indicator((guint32) false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,19 +316,19 @@ static void test_source(IBusKeymanTestsFixture *fixture, gconstpointer user_data
|
|||
g_settings_sync();
|
||||
|
||||
switch_keyboard(fixture, kmxfile.c_str());
|
||||
if (test_source.caps_lock_state() != get_caps_lock_state(fixture)) {
|
||||
set_caps_lock_state(fixture, test_source.caps_lock_state());
|
||||
if (test_source.caps_lock_state() != get_capslock_indicator()) {
|
||||
set_capslock_indicator((guint32)test_source.caps_lock_state());
|
||||
}
|
||||
|
||||
auto contextKeys = get_context_keys(context);
|
||||
for (auto k = contextKeys.begin(); k != contextKeys.end(); k++) {
|
||||
press_keys(fixture, test_source, *k);
|
||||
press_keys(fixture, *k);
|
||||
}
|
||||
|
||||
gtk_im_context_focus_in(fixture->context);
|
||||
|
||||
for (auto p = test_source.next_key(keys); p.vk != 0; p = test_source.next_key(keys)) {
|
||||
press_keys(fixture, test_source, p);
|
||||
press_keys(fixture, p);
|
||||
}
|
||||
|
||||
if (expected.length() == 0) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ android {
|
|||
productFlavors {
|
||||
}
|
||||
|
||||
String tier = new File('../../../TIER.md').getText('UTF-8').trim();
|
||||
applicationVariants.all { variant ->
|
||||
// Adjust output name to "firstvoices-${VERSION_MD}.apk"
|
||||
variant.outputs.all {
|
||||
|
|
@ -95,7 +96,7 @@ if (env_keys_json_file != null && file(env_keys_json_file).exists()) {
|
|||
|
||||
// Deactivate lower conflicting version APKs
|
||||
resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.IGNORE)
|
||||
switch (System.env.TIER) {
|
||||
switch (String.valueOf(tier)) {
|
||||
case 'beta':
|
||||
track.set("beta")
|
||||
println "TIER set to beta"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
|
@ -581,7 +581,7 @@
|
|||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -633,7 +633,7 @@
|
|||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
|
@ -653,7 +653,7 @@
|
|||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = D7TR486TEH;
|
||||
INFOPLIST_FILE = FirstVoices/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -682,7 +682,7 @@
|
|||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = D7TR486TEH;
|
||||
INFOPLIST_FILE = FirstVoices/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -709,7 +709,7 @@
|
|||
DEVELOPMENT_TEAM = D7TR486TEH;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -740,7 +740,7 @@
|
|||
DEVELOPMENT_TEAM = D7TR486TEH;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
INFOPLIST_FILE = SWKeyboard/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
KEYMAN_ROOT = "$(SRCROOT)/../../..";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,17 @@
|
|||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"src": [
|
||||
"**/src/",
|
||||
"src/**",
|
||||
"../common/**/src"
|
||||
],
|
||||
"include": [
|
||||
"src/**"
|
||||
],
|
||||
"exclude": [
|
||||
"src/test/**",
|
||||
"**/src/tests/**",
|
||||
"**/build/**",
|
||||
"src/tools/**",
|
||||
"../node_modules/*"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
28
web/build.sh
28
web/build.sh
|
|
@ -35,7 +35,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
|
|||
":engine/keyboard-storage Subset used to collate keyboards and request them from the cloud" \
|
||||
":engine/main Builds all common code used by KMW's app/-level targets" \
|
||||
":engine/osk Builds the Web OSK module" \
|
||||
":engine/predictive-text=src/engine/predictive-text/worker-main Builds KMW's predictive text module" \
|
||||
":engine/predictive-text Builds KMW's predictive text module" \
|
||||
":help Online documentation" \
|
||||
":samples Builds all needed resources for the KMW sample-page set" \
|
||||
":tools Builds engine-related development resources" \
|
||||
|
|
@ -141,12 +141,26 @@ test_action() {
|
|||
|
||||
coverage_action() {
|
||||
builder_echo "Creating coverage report..."
|
||||
cd "$KEYMAN_ROOT"
|
||||
mkdir -p web/build/coverage/tmp
|
||||
find . -type f -name coverage-\*.json -print0 | xargs -0 cp -t web/build/coverage/tmp
|
||||
c8 report --config web/.c8rc.json ---reporter html --clean=false --reports-dir=web/build/coverage
|
||||
rm -rf web/build/coverage/tmp
|
||||
cd web
|
||||
|
||||
# Always ensure our collation folder is clean, just in case something went wrong on a prior run.
|
||||
rm -rf build/coverage/tmp
|
||||
|
||||
# Also clean out the sub-report directories + main index.html; don't keep old ones if we fail!
|
||||
rm -rf build/coverage/app
|
||||
rm -rf build/coverage/engine
|
||||
rm -f build/coverage/index.html
|
||||
|
||||
# Collate the results into a single folder
|
||||
mkdir build/coverage/tmp
|
||||
find . -type f -name coverage-\*.json -print0 | xargs -0 cp -t build/coverage/tmp
|
||||
|
||||
# Note for maintainers: the .c8rc.json config needs to be written from the perspective of
|
||||
# the working directory - including its `include` and `exclude` entries. All ignores and
|
||||
# such must be redefined in whatever that top-level .c8rc.json is.
|
||||
c8 report --reporter html
|
||||
|
||||
# No need to keep the collation folder around afterward; we can always redo that work.
|
||||
rm -rf build/coverage/tmp
|
||||
}
|
||||
|
||||
builder_run_child_actions build:engine/dom-utils
|
||||
|
|
|
|||
|
|
@ -99,20 +99,11 @@ function test-headless() {
|
|||
fi
|
||||
|
||||
TEST_OPTS=
|
||||
TEST_CD_REQD=false
|
||||
if builder_has_option --ci; then
|
||||
TEST_OPTS="--reporter mocha-teamcity-reporter"
|
||||
fi
|
||||
if [[ -n "$TEST_EXTENSIONS" ]]; then
|
||||
TEST_OPTS="$TEST_OPTS --extension $TEST_EXTENSIONS"
|
||||
TEST_CD_REQD=true
|
||||
fi
|
||||
|
||||
if [ $TEST_CD_REQD ]; then
|
||||
# The mocha config needed to live-compile TS-based tests only applies
|
||||
# if the command is started within the appropriate subfolder.
|
||||
pushd "${TEST_BASE}" > /dev/null
|
||||
TEST_BASE=
|
||||
fi
|
||||
|
||||
if [[ -e .c8rc.json ]]; then
|
||||
|
|
@ -120,10 +111,6 @@ function test-headless() {
|
|||
else
|
||||
mocha --recursive "${TEST_BASE}${TEST_FOLDER}" $TEST_OPTS
|
||||
fi
|
||||
|
||||
if [ $TEST_CD_REQD ]; then
|
||||
popd > /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Runs all headless tests (written in typescript) corresponding to the
|
||||
|
|
|
|||
|
|
@ -6,21 +6,16 @@
|
|||
<script type='text/javascript' src='js/laokeys_load.js'></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var KWControl = null;
|
||||
|
||||
function SetupDocument()
|
||||
{
|
||||
keyman.init();
|
||||
KWControl = document.getElementById('KWControl');
|
||||
keyman.setActiveKeyboard('laokeys');
|
||||
keyman.osk.hide();
|
||||
keyman.init().then(function() {
|
||||
keyman.setActiveKeyboard('laokeys');
|
||||
keyman.osk.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function KWControlClick()
|
||||
{
|
||||
var KWControl = document.getElementById('KWControl');
|
||||
|
||||
if(keyman.osk.isEnabled()) {
|
||||
keyman.osk.hide();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,12 @@ Include the following script in the HEAD of your page:
|
|||
function SetupDocument()
|
||||
{
|
||||
/* Make sure that Keyman is initialized (we can't guarantee initialization order) */
|
||||
keyman.init();
|
||||
|
||||
KWControl = document.getElementById('KWControl');
|
||||
/* Prevents automatic display of the onscreen keyboard. (default automatic) */
|
||||
keyman.osk.hide();
|
||||
/* Select the LaoKeys keyboard */
|
||||
keyman.setActiveKeyboard('laokeys');
|
||||
keyman.init().then(function () {
|
||||
/* Prevents automatic display of the onscreen keyboard. (default automatic) */
|
||||
keyman.osk.hide();
|
||||
/* Select the LaoKeys keyboard */
|
||||
keyman.setActiveKeyboard('laokeys');
|
||||
});
|
||||
}
|
||||
|
||||
/* KWControlClick: Called when user clicks on the KWControl IMG */
|
||||
|
|
@ -56,7 +55,7 @@ And finally, include the control img for KeymanWeb:
|
|||
|
||||
```html
|
||||
<!-- Display the KeymanWeb icon for the user to click on -->
|
||||
<img style="border: solid 1px black; padding: 2px 2px 3px 2px" src='kmicon.png' alt='KeymanWeb' onclick='KWControlClick()' id='KWControl' />
|
||||
<img style="border: solid 1px black; padding: 2px 2px 3px 2px" src='kmicon.png' alt='KeymanWeb' onclick='KWControlClick()' />
|
||||
```
|
||||
|
||||
## API References
|
||||
|
|
|
|||
10
web/src/app/browser/.c8rc.json
Normal file
10
web/src/app/browser/.c8rc.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"check-coverage": false,
|
||||
"clean": true,
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "../../../build/coverage/raw/app/browser",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"clean": true,
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/engine/attachment",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
],
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/raw/engine/common/web-utils",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"clean": true,
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/engine/events",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"clean": true,
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "../../../build/engine/interfaces/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/engine/interfaces",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"clean": true,
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/engine/keyboard-storage",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
],
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../build/coverage/raw/engine/keyboard",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"src/engine/headless/*"
|
||||
],
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../../build/coverage/raw/engine/osk/gesture-processor",
|
||||
"src": [
|
||||
"src/engine/headless/*"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ builder_run_child_actions configure
|
|||
## Build actions
|
||||
|
||||
builder_run_child_actions build:wordbreakers
|
||||
|
||||
builder_run_child_actions build:templates
|
||||
builder_run_child_actions build:worker-thread
|
||||
builder_run_child_actions build:worker-main
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
],
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../../build/coverage/raw/engine/predictive-text/templates",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
],
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../../build/coverage/raw/engine/predictive-text/wordbreakers",
|
||||
"src": [
|
||||
"src/"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-
|
|||
export default class Worker {
|
||||
static constructInstance(): Worker {
|
||||
let scriptStr = unwrap(LMLayerWorkerCode);
|
||||
const srcMapUrlIndex = scriptStr.indexOf("//# sourceMappingURL")
|
||||
if(srcMapUrlIndex > 0) {
|
||||
scriptStr = scriptStr.substring(0, srcMapUrlIndex);
|
||||
}
|
||||
|
||||
scriptStr += '\n' + LMLayerWorkerSourcemapComment;
|
||||
let worker = new MappedWorker(scriptStr);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
],
|
||||
"exclude-after-remap": true,
|
||||
"reporter": ["text", "text-summary"],
|
||||
"reports-dir": "build/coverage",
|
||||
"reports-dir": "../../../../build/coverage/raw/engine/predictive-text/worker-thread",
|
||||
"src": [
|
||||
"src/main"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ export default {
|
|||
reporters: [
|
||||
teamcityReporter(), /* custom-written, for CI-friendly reports */
|
||||
sessionStabilityReporter({ciMode: true})
|
||||
]
|
||||
],
|
||||
concurrency: 1
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { assert } from 'chai';
|
||||
import fs from 'fs';
|
||||
|
||||
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
|
|
@ -9,6 +10,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
|
|||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
|
||||
|
||||
import { env } from 'node:process';
|
||||
const KEYMAN_ROOT = env.KEYMAN_ROOT;
|
||||
|
||||
describe('Engine - Basic Simulation', function() {
|
||||
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/basic_lao_simulation.json'));
|
||||
// Common test suite setup.
|
||||
|
|
@ -25,7 +29,7 @@ describe('Engine - Basic Simulation', function() {
|
|||
before(async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
|
||||
keyboardWithHarness = keyboardLoader.harness;
|
||||
keyboardWithHarness.activeKeyboard = keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'
|
|||
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
|
||||
import { ModifierKeyConstants } from '@keymanapp/common-types';
|
||||
|
||||
import { env } from 'node:process';
|
||||
const KEYMAN_ROOT = env.KEYMAN_ROOT;
|
||||
|
||||
describe('Engine - Chirality', function() {
|
||||
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/chirality.json'));
|
||||
// Common test suite setup.
|
||||
|
|
@ -26,7 +29,7 @@ describe('Engine - Chirality', function() {
|
|||
before(async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
|
||||
keyboardWithHarness = keyboardLoader.harness;
|
||||
keyboardWithHarness.activeKeyboard = keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
|
|||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
|
||||
|
||||
import { env } from 'node:process';
|
||||
const KEYMAN_ROOT = env.KEYMAN_ROOT;
|
||||
|
||||
describe('Engine - Deadkeys', function() {
|
||||
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/deadkeys.json'));
|
||||
// Common test suite setup.
|
||||
|
|
@ -25,7 +28,7 @@ describe('Engine - Deadkeys', function() {
|
|||
before(async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
|
||||
let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
|
||||
keyboardWithHarness = keyboardLoader.harness;
|
||||
keyboardWithHarness.activeKeyboard = keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
|
|||
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
|
||||
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
|
||||
|
||||
import { env } from 'node:process';
|
||||
const KEYMAN_ROOT = env.KEYMAN_ROOT;
|
||||
|
||||
describe('Engine - Unmatched Final Groups', function() {
|
||||
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/ghp_enter.json'));
|
||||
// Common test suite setup.
|
||||
|
|
@ -24,7 +27,7 @@ describe('Engine - Unmatched Final Groups', function() {
|
|||
before(async function() {
|
||||
// -- START: Standard Recorder-based unit test loading boilerplate --
|
||||
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
|
||||
const keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
|
||||
keyboardWithHarness = keyboardLoader.harness;
|
||||
keyboardWithHarness.activeKeyboard = keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ title: What's New in Keyman 18.0 for Windows
|
|||
|
||||
Here are some of the new features we have added to Keyman 18.0 for Windows:
|
||||
|
||||
- Minimum supported version of Windows is 10.0
|
||||
- Updates to Keyman are now applied before Keyman starts for the first time in a session, so Windows no longer needs to be restarted (#10041)
|
||||
- Keyman no longer adds a desktop shortcut when it is installed (#11401)
|
||||
- Added an option to make Right Alt and Right Control also work for keyboard switching hotkeys if preferred (#11471)
|
||||
|
||||
## Related Topics
|
||||
|
||||
- [Version History](history)
|
||||
|
|
|
|||
|
|
@ -327,6 +327,10 @@
|
|||
<string name="koShowWelcome" comment="Startup options - show/hide welcome screen">Zobrazit úvodní obrazovku</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Modified: 18.0.96.0 -->
|
||||
<string name="koCheckForUpdates" comment="Automatically check for updates and download">Automatically check for updates and download</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 7.0.236.0 -->
|
||||
<string name="koTestKeymanFunctioning" comment="Startup options - test for conflicting apps when Product starts">Test pro protichůdné aplikace při spuštění klíče</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
|
|
@ -454,6 +458,22 @@
|
|||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="S_Menu_Diagnostics_Diagnostics" comment="Support button submenu - performs a diagnostic report">Diagnostika</string>
|
||||
<!-- Context: Configuration Dialog - Update tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_No_Updates_Available" comment="Update tab - no updates are available">Žádné dostupné aktualizace.</string>
|
||||
<!-- Context: Configuration Dialog - Update tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">K dispozici jsou aktualizace, které budou aplikovány při příštím restartu Windows.</string>
|
||||
<!-- Context: Configuration Dialog - Update tab pop up -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Update_Restart_Req" comment="Update tab pop up - warns user restart will require restart ">Aktualizace nyní vyžaduje restart počítače.</string>
|
||||
<!-- Context: Configuration Dialog - Update on boot pop up -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Ready_To_Install" comment="Update on boot pop up - inform user update about to install">Aktualizace Keyman je připravena k instalaci.</string>
|
||||
<!-- Context: Download Keyboard Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
|
|
@ -546,6 +566,10 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky
|
|||
<string name="SKHotkeyConflicts_Interface" comment="Hotkey dialog - warning of hotkey conflict with hotkey for another interface hotkey">Klávesa %1$s je v konfliktu s jiným klávesovým zkratkou. Pokud budete pokračovat, ostatní klávesová zkratka bude vymazána. Pokračovat?</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.230.0 -->
|
||||
<string name="S_Update" comment="Update tab name">Aktualizovat</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="S_Update_Title" comment="Update dialog title, where Keyman = product name">K dispozici jsou aktualizované komponenty klíče</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
|
|
@ -615,6 +639,14 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky
|
|||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 8.0.288.0 -->
|
||||
<string name="SKUpdate_IconMenuExit" comment="Update icon - exit menu item">E&xit online update check</string>
|
||||
<!-- Context: Update Dialog -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 17.0233.0 -->
|
||||
<string name="S_Button_Update_ApplyNow" comment="Apply update now button - update dialog">Aplikovat aktualizaci nyní</string>
|
||||
<!-- Context: Update Dialog -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 17.0233.0 -->
|
||||
<string name="S_Button_Update_CheckNow" comment="Check for updates now button - update dialog">Zkontrolovat aktualizace</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 8.0.288.0 -->
|
||||
|
|
|
|||
|
|
@ -295,6 +295,10 @@
|
|||
<string name="koAltGrCtrlAlt" comment="General options - Ctrl+Alt simulates AltGr on computers without AltGr">ပချဳ AltGr နကဵု Ctrl+Alt</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 18.0.95 -->
|
||||
<string name="koRightModifierHK" comment="General options - Right Ctrl Alt and Shift trigger hotkey">ကဵုအခေါၚ်ကု မဝ်ဒဳဝှာၚ်ယျာ လ္ပါ်ပုၚ် သွက် မဍဵုတၟေၚ်</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 9.0.480.0 -->
|
||||
<string name="koDeadkeyConversion" comment="Advanced options - Base keyboard deadkeys are treated as normal keys">စွံ မဍဵုချိုတ်တ် နကဵု မဍဵု အလောတ်</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
|
|
@ -323,6 +327,10 @@
|
|||
<string name="koShowWelcome" comment="Startup options - show/hide welcome screen">ထ္ၜးမုက်လိက် ဒုၚ်တၠုၚ်</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Modified: 18.0.96.0 -->
|
||||
<string name="koCheckForUpdates" comment="Automatically check for updates and download">Automatically check for updates and download</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 7.0.236.0 -->
|
||||
<string name="koTestKeymanFunctioning" comment="Startup options - test for conflicting apps when Product starts">စၟဳစၟတ် အပလဳကေယှေန် ပဠပထုဲမာန် အဃောစ ကဳမာန်</string>
|
||||
<!-- Context: Configuration Dialog - Options tab -->
|
||||
|
|
@ -416,6 +424,14 @@
|
|||
<string name="S_Support_Copyright" comment="Support - Product Copyright">တၠမူ © SIL International. အခေါၚ်အဝဵုနွံပေၚ်ၚ်။</string>
|
||||
<!-- Context: Configuration Dialog - Support tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.94.0 -->
|
||||
<string name="S_Support_CreatedBySILGlobal" comment="Created by {SIL Global} ">ညးကၠောန်ဗဒှ်%1$s</string>
|
||||
<!-- Context: Configuration Dialog - Support tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.94.0 -->
|
||||
<string name="S_Support_CopyrightSILGlobal" comment="Copyright © {SIL Global}. All Rights Reserved.">တၠမူ © %1$s။ အခေါၚ်အရာဖအိုတ်ဂှ် ညးပိုၚ်ရောၚ်။</string>
|
||||
<!-- Context: Configuration Dialog - Support tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="S_Support_Version" comment="Support - version">ပါယှေန်</string>
|
||||
<!-- Context: Configuration Dialog - Support tab -->
|
||||
|
|
@ -442,6 +458,22 @@
|
|||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="S_Menu_Diagnostics_Diagnostics" comment="Support button submenu - performs a diagnostic report">ဂၠိုၚ်ဂၠါဲပြဿနာ</string>
|
||||
<!-- Context: Configuration Dialog - Update tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_No_Updates_Available" comment="Update tab - no updates are available">တၚ်သမၠုၚ်က္ဆံၚ်ဟွံမွဲဏီ။</string>
|
||||
<!-- Context: Configuration Dialog - Update tab -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">သမၠုၚ်ပ္တိုန်တံဂှ် ယဝ်ကၟာတ်ပံက်ထောံ မုက်လိက်တုဲမ္ဂး ဒှ်ဒတန်ရ။</string>
|
||||
<!-- Context: Configuration Dialog - Update tab pop up -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Update_Restart_Req" comment="Update tab pop up - warns user restart will require restart ">သမၠုၚ်ပ္တိုန်လၟုဟ်မ္ဂး ဒးကၟာတ်ပံက်စက်ဏောၚ်။ </string>
|
||||
<!-- Context: Configuration Dialog - Update on boot pop up -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.168 -->
|
||||
<string name="S_Ready_To_Install" comment="Update on boot pop up - inform user update about to install">တၚ်သမၠုၚ်က္ဆံၚ်သွက်ကဳမာန် စုတ်မာန်ရ။</string>
|
||||
<!-- Context: Download Keyboard Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
|
|
@ -533,6 +565,10 @@
|
|||
<string name="SKHotkeyConflicts_Interface" comment="Hotkey dialog - warning of hotkey conflict with hotkey for another interface hotkey">မဍဵုတၟေၚ် %1$s ဒှ်ပြဿနာကု မဍဵုတၟေၚ်တၞဟ်ဟ်ရ။ ယဝ်ဆက်အာမ္ဂး မဍဵုတၟေၚ်တၞဟ်ဟ်ဂှ် သ္အးထောံရောၚ်။ ဆက်အာဟာ\?</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0.230.0 -->
|
||||
<string name="S_Update" comment="Update tab name">တၚ်သမၠုၚ်ပ္တိုန်</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="S_Update_Title" comment="Update dialog title, where Keyman = product name">ကပေါတ်တၟိသွက်ကဳမာန်တံကလိဂွံမာန်ယျ</string>
|
||||
<!-- Context: Online Update Dialog -->
|
||||
|
|
@ -602,16 +638,27 @@
|
|||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 8.0.288.0 -->
|
||||
<string name="SKUpdate_IconMenuExit" comment="Update icon - exit menu item">ဗ&ဒေါံ စၟဳစၟတ်သမၠုၚ်က္ဆံၚ်အောန်လာၚ်</string>
|
||||
<!-- Context: Update Dialog -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 17.0233.0 -->
|
||||
<string name="S_Button_Update_ApplyNow" comment="Apply update now button - update dialog">ပ္တိုန်တၚ်သမၠုၚ်စက်လၟုဟ်</string>
|
||||
<!-- Context: Update Dialog -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 17.0233.0 -->
|
||||
<string name="S_Button_Update_CheckNow" comment="Check for updates now button - update dialog">စၟဳစၟတ်သွက် တၚ်သမၠုၚ်ပ္တိုန်တၟိတၟိ</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 8.0.288.0 -->
|
||||
<string name="S_Splash_Name" comment="Splash major title">ကဳမာန်</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 8.0.294.0 -->
|
||||
<string name="S_Splash_Start" comment="Start Keyman button">စ ကဳမာန်</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 18.0 -->
|
||||
<string name="S_Splash_Start_2" comment="Start {Keyman} button">စ %1$s</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 8.0.294.0 -->
|
||||
<string name="S_Splash_Exit" comment="Exit button">တိတ်</string>
|
||||
<!-- Context: Splash Dialog -->
|
||||
|
|
@ -653,7 +700,6 @@
|
|||
<!-- Context: Formatted Messages -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="SKShortApplicationTitle" comment="Product name">ကဳမာအ်</string>
|
||||
<!-- Context: Hints -->
|
||||
<!-- String Type: PlainText -->
|
||||
<!-- Introduced: 7.0.244.0 -->
|
||||
|
|
@ -777,7 +823,6 @@
|
|||
<!-- Context: Formatted Messages -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
<string name="SKApplicationTitle" comment="Product name used for the title of message boxes and message dialogs">ကဳမာန်</string>
|
||||
<!-- Parameters: %1$s = Version number string -->
|
||||
<!-- Context: Formatted Messages -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
|
|
@ -900,6 +945,10 @@
|
|||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 8.0.294.0 -->
|
||||
<string name="S_OSK_FontHelper_ChooseKeyboard" comment="OSK Font helper choose keyboard">သ္ဂောံဆဵု မလိက်ကောန်ဍောၚ် မဆေၚ်စပ်တံဂှ် ရုဲကဵု ကဳဗုဒ် ကဳမာန်တံညိ။</string>
|
||||
<!-- Context: OSK Font Helper -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 17.0.312.0 -->
|
||||
<string name="S_OSK_FontHelper_NoFonts" comment="OSK Font helper no fonts found for keyboard">မလိက်ဝှတ် မကဵုဖျုန်ကဵု သွက်ကဳဗုက် %1$s ဟွံဆဵုရ.</string>
|
||||
<!-- Context: Text Editor -->
|
||||
<!-- String Type: FormatString -->
|
||||
<!-- Introduced: 10.0.836.0 -->
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue