diff --git a/HISTORY.md b/HISTORY.md index aea57b9ea8..44ebb2af0e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,53 @@ # Keyman Version History +## 17.0.74 alpha 2023-03-22 + +* chore(windows): update sentry-native to 0.6.0 (#8464) +* chore(linux): Prevent building on s390x (#8477) +* fix(linux): Display error message for corrupt .kmp file (#8479) + +## 17.0.73 alpha 2023-03-21 + +* refactor(android/engine): Consolidate insertText (#8438) +* chore(common): prevent multiple npm ci runs in child scripts (#8484) + +## 17.0.72 alpha 2023-03-20 + +* chore(common): die early when --ci and --debug passed to test.sh (#8465) +* chore(common): Update crowdin strings for Kibaku (#8447) + +## 17.0.71 alpha 2023-03-18 + +* fix(core): ldml update fr-azerty for VKEY mapping (#8434) + +## 17.0.70 alpha 2023-03-17 + +* chore(android): Document builder script steps (#8449) + +## 17.0.69 alpha 2023-03-16 + +* chore(web): Cleanup build echo (#8446) +* fix(developer): lm compiler handle missing line no in errors (#8444) +* fix(android/app): Temporarily disable Keyman browser (#8430) +* fix(android/engine): Add builder output for configure (#8442) +* chore(linux): Update debian changelog (#8452) + +## 17.0.68 alpha 2023-03-15 + +* chore(common): add common test build configurations (#8431) +* fix(common): fix broken common/web/types cases (#8426) +* fix(developer/compilers): locks esbuild target detection for kmc building (#8437) +* (#8412) +* (#8436) +* chore(linux): Use dependency on core in ibus-keyman/build.sh (#8423) + +## 17.0.67 alpha 2023-03-14 + +* (developer):Update copyright period in License.rtf (#8425) +* chore(common): support shorthand for build.sh (#8415) +* chore(common): build script performance improvements (#8416) +* chore(common): TS updates to non-sync'd packages, feature-esmodule merge conflict prevention (#8429) + ## 17.0.66 alpha 2023-03-13 * refactor(android): Use builder scripts (#7407) diff --git a/VERSION.md b/VERSION.md index 710b4d8ff4..4dc77ece12 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.67 \ No newline at end of file +17.0.75 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 1ac5cf3e0f..e112a6360d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -435,9 +435,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene case R.id.action_share: showShareDialog(); return true; + /* Disable Web Browser to investigate Google sign-in case R.id.action_web: showWebBrowser(); - return true; + return true;*/ case R.id.action_text_size: showTextSizeDialog(); return true; diff --git a/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml b/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml index f1f9e614a1..bce17bc43a 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml @@ -8,12 +8,13 @@ app:showAsAction="always" android:title="@string/action_share" android:icon="@drawable/ic_light_action_share" /> - - + - - + - - + + Ndǝɦǝnta @@ -25,6 +26,7 @@ Badicini taiping akwani… + Ngakurǝr Text: %1$d @@ -82,7 +84,7 @@ Ta gǝta tsanti kapshion ata spesba - Vibrate when typing + Ata kǝngǝri ko tuwa ma gata kǝra ʻya taiping Tsanti banner kǝlara saka @@ -154,7 +156,7 @@ Invalid/Missing metadata in package - Keyboard requires a newer version of Keyman + Kibod ǝni ada kǝthlǝr aka jiliyir Keyman nam bǝlin. - Unable to launch web browser + Yar ta mǝzi ka mburnta brauza wɛ diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 2c137f971f..65fe3a9e5e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -67,12 +67,12 @@ import io.sentry.SentryLevel; final class KMKeyboard extends WebView { private static final String TAG = "KMKeyboard"; private final Context context; - private KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED; private String packageID; private String keyboardID; private String keyboardName; private String keyboardVersion; + protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED; protected ArrayList javascriptAfterLoad = new ArrayList(); private static String currentKeyboard = null; diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 47d7d03231..7098f2c3dc 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -2,15 +2,27 @@ package com.keyman.engine; import android.content.Context; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.DisplayMetrics; import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; import android.webkit.JavascriptInterface; -import android.widget.RelativeLayout; import static android.content.Context.VIBRATOR_SERVICE; +import com.keyman.engine.KMManager.KeyboardType; +import com.keyman.engine.util.CharSequenceUtil; +import com.keyman.engine.util.KMLog; + public abstract class KMKeyboardJSHandler { private Context context; private KMKeyboard k = null; @@ -64,6 +76,126 @@ public abstract class KMKeyboardJSHandler { } } + /** + * Inserts the selected string s + * @param dn Number of pre-caret code points (UTF+8 characters) to delete + * @param s Text to insert + * @param dr Number of post-caret code points to delete. + */ + @JavascriptInterface + public void insertText(final int dn, final String s, final int dr, final boolean executingHardwareKeystroke) { + Handler mainLoop = new Handler(Looper.getMainLooper()); + mainLoop.post(new Runnable() { + public void run() { + if (k == null) { + KMLog.LogError(TAG, "insertText failed: Keyboard is null"); + return; + } + + if (k.subKeysWindow != null) { + return; + } + + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && + (KMTextView.activeView == null || KMTextView.activeView.getClass() != KMTextView.class)) { + if (KMTextView.activeView == null && KMManager.isDebugMode()) { + Log.w(TAG, "insertText failed: activeView is null"); + } + return; + } + + InputConnection ic = (k.keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) ? + KMTextView.activeView.onCreateInputConnection(new EditorInfo()) : + KMManager.getInputMethodService().getCurrentInputConnection(); + if (ic == null) { + if (KMManager.isDebugMode()) { + Log.w(TAG, "insertText failed: InputConnection is null"); + } + return; + } + + ic.beginBatchEdit(); + + int deleteLeft = dn; + + // Delete any existing selected text. + ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); + if (icText != null) { // This can be null if the input connection becomes invalid. + int start = icText.startOffset + icText.selectionStart; + int end = icText.startOffset + icText.selectionEnd; + if (end < start) { + // Swap start/end for backward selection + int temp = start; + start = end; + end = temp; + } + if (end > start) { + if (s.length() == 0) { + ic.setSelection(start, start); + ic.deleteSurroundingText(0, end - start); + ic.endBatchEdit(); + return; + } else { + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + KMManager.SystemKeyboardShouldIgnoreSelectionChange = true; + } + ic.setSelection(start, start); + ic.deleteSurroundingText(0, end - start); + } + + // KeymanWeb tells us how to delete the selection, but we don't + // want to do that twice + deleteLeft = 0; + } + } + + if (s.length() > 0 && s.charAt(0) == '\n') { + keyDownUp(KeyEvent.KEYCODE_ENTER); + ic.endBatchEdit(); + return; + } + + // Perform left-deletions + if (deleteLeft > 0) { + performLeftDeletions(ic, deleteLeft); + } + + // Perform right-deletions + for (int i = 0; i < dr; i++) { + CharSequence chars = ic.getTextAfterCursor(1, 0); + if (chars != null && chars.length() > 0) { + char c = chars.charAt(0); + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + KMManager.SystemKeyboardShouldIgnoreSelectionChange = true; + } + if (Character.isHighSurrogate(c)) { + ic.deleteSurroundingText(0, 2); + } else { + ic.deleteSurroundingText(0, 1); + } + } + } + + if (s.length() > 0) { + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + KMManager.SystemKeyboardShouldIgnoreSelectionChange = true; + } + // Commit the string s. Use newCursorPosition 1 so cursor will end up after the string. + ic.commitText(s, 1); + } + + k.dismissHelpBubble(); + k.setShouldShowHelpBubble(false); + + ic.endBatchEdit(); + ViewGroup parent = (ViewGroup) k.getParent(); + if (parent != null && KMManager.getHapticFeedback() && !executingHardwareKeystroke) { + parent.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } + } + }); + } + // Store the current keyboard chirality status from KMW in the Keyboard @JavascriptInterface public void setIsChiral(boolean isChiral) { @@ -73,14 +205,90 @@ public abstract class KMKeyboardJSHandler { } @JavascriptInterface - public abstract boolean dispatchKey(final int code, final int eventModifiers); + public abstract boolean dispatchKey(final int code, final int eventModifiers); + + private void keyDownUp(int keyEventCode) { + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) { + KMTextView textView = (KMTextView)KMTextView.activeView; + textView.keyDownUp(KeyEvent.KEYCODE_ENTER); + } else if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + KMManager.getInputMethodService().getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); + KMManager.getInputMethodService().getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); + } + } + + /* + // Chromium up until version M81 had a bug where deleteSurroundingText deletes an entire + // grapheme cluster instead of one code-point. See Chromium issue #1024738 + // https://bugs.chromium.org/p/chromium/issues/detail?id=1024738 + // + // We'll retrieve up to (dn*2+16) characters before the cursor to collect enough characters + // for surrogate pairs + a long grapheme cluster. + // This buffer will be used to put back characters as-needed + */ + private static void performLeftDeletions(InputConnection ic, int dn) { + int originalBufferLength = dn*2 + 16; // characters + CharSequence charsBackup = getCharacterSequence(ic, originalBufferLength); + + int lastIndex = charsBackup.length()-1; + + // Exit if there's no context to delete + if (lastIndex < 0) { + return; + } + + // Count the number of characters which are surrogate pairs + int numPairs = CharSequenceUtil.countSurrogatePairs(charsBackup, dn); + + // Chop dn+numPairs code points from the end of charsBackup + // subSequence indices are start(inclusive) to end(exclusive) + CharSequence expectedChars = charsBackup.subSequence(0, charsBackup.length() - (dn + numPairs)); + ic.deleteSurroundingText(dn + numPairs, 0); + CharSequence newContext = getCharacterSequence(ic, originalBufferLength - 2*dn); + + CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, newContext); + if (charsToRestore.length() > 0) { + // Restore expectedChars that Chromium deleted. + // Use newCusorPosition 1 so cursor will be after the inserted string + ic.commitText(charsToRestore, 1); + } + } /** - * Inserts the selected string s - * @param dn Number of pre-caret code points (UTF+8 characters) to delete - * @param s Text to insert - * @param dr Number of post-caret code points to delete. + * Get a character sequence from the InputConnection. + * Sometimes the WebView can split a surrogate pair at either end, + * so chop that and update the cursor + * @param ic - the InputConnection + * @param length - number of characters to get + * @return CharSequence */ - @JavascriptInterface - public abstract void insertText(final int dn, final String s, final int dr, final boolean executingHardwareKeystroke); + private static CharSequence getCharacterSequence(InputConnection ic, int length) { + if (ic == null || length <= 0) { + return ""; + } + + CharSequence sequence = ic.getTextBeforeCursor(length, 0); + if (sequence == null || sequence.length() <= 0) { + return ""; + } + + // Move the cursor back if there's a split surrogate pair + if (Character.isHighSurrogate(sequence.charAt(sequence.length()-1))) { + ic.commitText("", -1); + sequence = ic.getTextBeforeCursor(length, 0); + } + + if (sequence == null || sequence.length() <= 0) { + return ""; + } + + if (Character.isLowSurrogate(sequence.charAt(0))) { + // Adjust if the first char is also a split surrogate pair + // subSequence indices are start(inclusive) to end(exclusive) + sequence = sequence.subSequence(1, sequence.length()); + } + + return sequence; + } + } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index b8e115f3e8..16944ac860 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2264,124 +2264,6 @@ public final class KMManager { }); return true; } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public void insertText(final int dn, final String s, final int dr, final boolean executingHardwareKeystroke) { - if(dr != 0) { - Log.d(TAG, "Right deletions requested but are not presently supported by the in-app keyboard."); - } - - Handler mainLoop = new Handler(Looper.getMainLooper()); - mainLoop.post(new Runnable() { - public void run() { - if (InAppKeyboard == null) { - KMLog.LogError(TAG, "insertText failed: InAppKeyboard is null"); - return; - } - - if (InAppKeyboard.subKeysWindow != null || KMTextView.activeView == null || KMTextView.activeView.getClass() != KMTextView.class) { - if ((KMTextView.activeView == null) && isDebugMode()) { - Log.w("IAK: JS Handler", "insertText failed: activeView is null"); - } - return; - } - - InAppKeyboard.dismissHelpBubble(); - InAppKeyboard.setShouldShowHelpBubble(false); - - KMTextView textView = (KMTextView) KMTextView.activeView; - textView.beginBatchEdit(); - - int start = textView.getSelectionStart(); - int end = textView.getSelectionEnd(); - // Workaround for Android TextView bug where end < start - // Reference: https://issuetracker.google.com/issues/36911048 - if (end < start) { - Log.d(TAG, "Swapping TextView selection end:" + end + " and start:" + start); - int temp = end; - end = start; - start = temp; - } - - int deleteLeft = dn; - - if(start != end && dn == 1 && s.length() == 0) { - /* Handle backspace with a selection: just delete selection */ - deleteLeft = 0; - } - - if (deleteLeft <= 0) { - if (start == end) { - if (s.length() > 0 && s.charAt(0) == '\n') { - textView.keyDownUp(KeyEvent.KEYCODE_ENTER); - } else if (s.length() > 0) { - // *** TO DO: Try to find a solution to the bug on API < 17, insert overwrites on next line - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - textView.getText().insert(start, s); - } else { - textView.getText().delete(start, end); - } - } else { - if (s.length() > 0 && s.charAt(0) == '\n') { - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - textView.getText().replace(start, end, ""); - textView.keyDownUp(KeyEvent.KEYCODE_ENTER); - } else { - if (s.length() == 0) { - textView.getText().delete(start, end); - } else { - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - textView.getText().replace(start, end, s); - } - } - } - } else { - if(start != end) { - // Delete the selection - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - textView.getText().delete(start, end); - textView.setSelection(start); - end = start; - deleteLeft = 0; - } - for (int i = 0; i < deleteLeft; i++) { - CharSequence chars = textView.getText().subSequence(0, start); - if (chars != null && chars.length() > 0) { - char c = chars.charAt(start - 1); - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - if (Character.isLowSurrogate(c)) { - textView.getText().delete(start - 2, end); - } else { - textView.getText().delete(start - 1, end); - } - - start = textView.getSelectionStart(); - end = textView.getSelectionEnd(); - } - } - - if (s.length() > 0) { - InAppKeyboardShouldIgnoreTextChange = true; - InAppKeyboardShouldIgnoreSelectionChange = true; - textView.getText().insert(start, s); - } - } - - // Collapse the selection - textView.setSelection(start + s.length()); - textView.endBatchEdit(); - if (mayHaveHapticFeedback && !executingHardwareKeystroke) { - textView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } - } - }); - } } private static final class KMSystemKeyboardJSHandler extends KMKeyboardJSHandler { @@ -2428,185 +2310,5 @@ public final class KMManager { }); return true; } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public void insertText(final int dn, final String s, final int dr, final boolean executingHardwareKeystroke) { - // TODO: Unify in-app and system insertText - Handler mainLoop = new Handler(Looper.getMainLooper()); - mainLoop.post(new Runnable() { - public void run() { - if (SystemKeyboard == null) { - KMLog.LogError(TAG, "insertText failed: SystemKeyboard is null"); - return; - } - - if (SystemKeyboard.subKeysWindow != null) { - return; - } - - InputConnection ic = IMService.getCurrentInputConnection(); - if (ic == null) { - if (isDebugMode()) { - Log.w(HANDLER_TAG, "insertText failed: InputConnection is null"); - } - return; - } - - ic.beginBatchEdit(); - - int deleteLeft = dn; - - // Delete any existing selected text. - ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); - if (icText != null) { // This can be null if the input connection becomes invalid. - int start = icText.startOffset + icText.selectionStart; - int end = icText.startOffset + icText.selectionEnd; - if (end < start) { - // Swap start/end for backward selection - int temp = start; - start = end; - end = temp; - } - if (end > start) { - if (s.length() == 0) { - ic.setSelection(start, start); - ic.deleteSurroundingText(0, end - start); - ic.endBatchEdit(); - return; - } else { - SystemKeyboardShouldIgnoreSelectionChange = true; - ic.setSelection(start, start); - ic.deleteSurroundingText(0, end - start); - } - - // KeymanWeb tells us how to delete the selection, but we don't - // want to do that twice - deleteLeft = 0; - } - } - - if (s.length() > 0 && s.charAt(0) == '\n') { - keyDownUp(KeyEvent.KEYCODE_ENTER); - ic.endBatchEdit(); - return; - } - - // Perform left-deletions - if (deleteLeft > 0) { - performLeftDeletions(ic, deleteLeft); - } - - // Perform right-deletions - for (int i = 0; i < dr; i++) { - CharSequence chars = ic.getTextAfterCursor(1, 0); - if (chars != null && chars.length() > 0) { - char c = chars.charAt(0); - SystemKeyboardShouldIgnoreSelectionChange = true; - if (Character.isHighSurrogate(c)) { - ic.deleteSurroundingText(0, 2); - } else { - ic.deleteSurroundingText(0, 1); - } - } - } - - if (s.length() > 0) { - SystemKeyboardShouldIgnoreSelectionChange = true; - - // Commit the string s. Use newCursorPosition 1 so cursor will end up after the string. - ic.commitText(s, 1); - } - - SystemKeyboard.dismissHelpBubble(); - SystemKeyboard.setShouldShowHelpBubble(false); - - ic.endBatchEdit(); - ViewGroup parent = (ViewGroup) SystemKeyboard.getParent(); - if (parent != null && mayHaveHapticFeedback && !executingHardwareKeystroke) { - parent.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } - } - }); - } - - private void keyDownUp(int keyEventCode) { - IMService.getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); - IMService.getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); - } - - /* - // Chromium up until version M81 had a bug where deleteSurroundingText deletes an entire - // grapheme cluster instead of one code-point. See Chromium issue #1024738 - // https://bugs.chromium.org/p/chromium/issues/detail?id=1024738 - // - // We'll retrieve up to (dn*2+16) characters before the cursor to collect enough characters - // for surrogate pairs + a long grapheme cluster. - // This buffer will be used to put back characters as-needed - */ - private static void performLeftDeletions(InputConnection ic, int dn) { - int originalBufferLength = dn*2 + 16; // characters - CharSequence charsBackup = getCharacterSequence(ic, originalBufferLength); - - int lastIndex = charsBackup.length()-1; - - // Exit if there's no context to delete - if (lastIndex < 0) { - return; - } - - // Count the number of characters which are surrogate pairs - int numPairs = CharSequenceUtil.countSurrogatePairs(charsBackup, dn); - - // Chop dn+numPairs code points from the end of charsBackup - // subSequence indices are start(inclusive) to end(exclusive) - CharSequence expectedChars = charsBackup.subSequence(0, charsBackup.length() - (dn + numPairs)); - ic.deleteSurroundingText(dn + numPairs, 0); - CharSequence newContext = getCharacterSequence(ic, originalBufferLength - 2*dn); - - CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, newContext); - if (charsToRestore.length() > 0) { - // Restore expectedChars that Chromium deleted. - // Use newCusorPosition 1 so cursor will be after the inserted string - ic.commitText(charsToRestore, 1); - } - } - - /** - * Get a character sequence from the InputConnection. - * Sometimes the WebView can split a surrogate pair at either end, - * so chop that and update the cursor - * @param ic - the InputConnection - * @param length - number of characters to get - * @return CharSequence - */ - private static CharSequence getCharacterSequence(InputConnection ic, int length) { - if (ic == null || length <= 0) { - return ""; - } - - CharSequence sequence = ic.getTextBeforeCursor(length, 0); - if (sequence == null || sequence.length() <= 0) { - return ""; - } - - // Move the cursor back if there's a split surrogate pair - if (Character.isHighSurrogate(sequence.charAt(sequence.length()-1))) { - ic.commitText("", -1); - sequence = ic.getTextBeforeCursor(length, 0); - } - - if (sequence == null || sequence.length() <= 0) { - return ""; - } - - if (Character.isLowSurrogate(sequence.charAt(0))) { - // Adjust if the first char is also a split surrogate pair - // subSequence indices are start(inclusive) to end(exclusive) - sequence = sequence.subSequence(1, sequence.length()); - } - - return sequence; - } } } diff --git a/android/KMEA/app/src/main/res/values-ckl-rNG/strings.xml b/android/KMEA/app/src/main/res/values-ckl-rNG/strings.xml index 09dd795b87..61768013b7 100644 --- a/android/KMEA/app/src/main/res/values-ckl-rNG/strings.xml +++ b/android/KMEA/app/src/main/res/values-ckl-rNG/strings.xml @@ -9,8 +9,8 @@ - Other Input Method - Other Input Methods + Whalǝr ʻYa ko pu Suyere Damwa + Whalǝr ʻYa ko Pu Suyere Dzakǝr Kibod Ɓǝlin @@ -45,7 +45,7 @@ Update - No internet connection + Layǝr intanet adiwɛ Adi ata mǝzi ɓui ka lukwa Keyman server wɛ! @@ -84,7 +84,7 @@ Su kura kal kalakwa kibod %1$s:%2$s nǝr %3$s nya. Ata kǝra chakǝnta akuyaka ɓui suyere tara tsukwar bwar dictionary kadǝbar ka ndǝ download ko sǝgǝl akǝr - Cannot connect to Keyman server to check for associated dictionary to download + Gadiyata mǝzi bui sava nǝr Keyman wɛ akuya ka gǝ bui ka sǝgǝl aka jiliyir dikshinari namtǝ gada bara. Gada bara download vershon nam ɓǝlin nǝr kibod ǝni ya? @@ -128,11 +128,11 @@ Gada bara kǝlba ka ɗalba dictionary ǝni ya? - Dictionary deleted + Ndɛ kǝlba ka ɗalba dikshinari. %1$s ndǝ kurta vu kibod - Keyboard deleted + Ndǝ kǝlba ka ɗalba Kibod Mǝrti ka dza mǝzi ʻyati ka dzǝ dzukti suwar su wato correctionyere @@ -140,12 +140,12 @@ Dictionary - Dictionary - Dictionaries + Dikshinari + Dikshinariyere Baraba ka zǝndi dictionary nam adai - Check for dictionaries online + Wulba ka gǝ bui dikshinariyere akwa layir shambar Dictionary: %1$s @@ -168,5 +168,5 @@ Tsukwar vi ǝni akuya ka gǝ pali kibod - Unable to launch web browser + Yar ta mǝzi ka mburnta brauza wɛ diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index ecfba488bc..4ddc7a206b 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -49,6 +49,7 @@ if builder_is_debug_build; then fi builder_describe_outputs \ + configure:engine /android/KMEA/app/src/main/assets/keymanandroid.js \ build:engine /android/KMEA/app/build/outputs/aar/${CONFIG}/keyman-engine.aar #### Build diff --git a/android/README.md b/android/README.md index a75c769e56..ffdbb4a015 100644 --- a/android/README.md +++ b/android/README.md @@ -25,9 +25,7 @@ Keyman for Android uses [Sentry](https://sentry.io) for crash reporting at a ser * Compile KMEA (and its KMW dependency) * Download default keyboard and dictionary resources as needed * Compile KMAPro - * Note: to force an update to the latest keyboard and dictionary packages, use the `--download-resources` flag. - -3. The APK will be found in **keyman/android/KMAPro/kMAPro/build/outputs/apk/debug/kMAPro-debug.apk** +3. The APK will be found in **keyman/android/KMAPro/kMAPro/build/outputs/apk/debug/keyman-${version}.apk** where `${version}` is the current version number. ### Compiling From Android Studio 1. Ensure that [Keyman Engine for Android](#how-to-build-keyman-engine-for-android) is built. @@ -60,10 +58,11 @@ Keyman for Android uses [Sentry](https://sentry.io) for crash reporting at a ser ### Compiling the app's offline help Keyman for Android help is maintained in the Markdown files in android/help/. -The script `build-help.sh` uses the `pandoc` tool to convert the Markdown files into html. +The script `/resources/build/build-help.inc.sh` uses the `pandoc` tool to convert the Markdown files into html. ```bash -./build-help.sh htm + # Convert markdown to html for offline help + build_help_html android KMAPro/kMAPro/src/main/assets/info ``` This script is automatically called when Keyman for Android is built. @@ -80,7 +79,7 @@ Both sample apps include a default Tamil keyboard and sample dictionary. Building these projects follow the same steps as KMAPro: 1. cd to the desired KMSample directory -2. `./build.sh` +2. `./build.sh configure build --debug` 3. Open Android Studio to run the app ### Tests: KeyboardHarness @@ -93,7 +92,7 @@ Building these projects follow the same steps as KMAPro: * Build the keyboardharness.kmp keyboard package 3. Add the keyboard in *android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java* 4. cd to android/Tests/KeyboardHarness/ -5. `./build.sh` +5. `./build.sh configure build --debug` 6. Open Android Studio to run the app -------------------------------------------------------------- diff --git a/android/Samples/KMSample1/build.sh b/android/Samples/KMSample1/build.sh index 3edcb44e01..35f006de66 100755 --- a/android/Samples/KMSample1/build.sh +++ b/android/Samples/KMSample1/build.sh @@ -29,6 +29,7 @@ builder_describe "Build KMSample1 app for Android." \ "clean" \ "configure" \ "build" \ + "test" \ ":app KMSample1" \ "--ci Don't start the Gradle daemon. Use for CI" @@ -75,6 +76,10 @@ fi if builder_start_action build:app; then ./gradlew clean $SAMPLE_FLAGS - builder_finish_action success build:app fi + +if builder_start_action test:app; then + # TODO: define tests + builder_finish_action success test:app +fi diff --git a/android/Samples/KMSample2/build.sh b/android/Samples/KMSample2/build.sh index 608076ee57..d49ad74749 100755 --- a/android/Samples/KMSample2/build.sh +++ b/android/Samples/KMSample2/build.sh @@ -29,6 +29,7 @@ builder_describe "Build KMSample2 app for Android." \ "clean" \ "configure" \ "build" \ + "test" \ ":app KMSample2" \ "--ci Don't start the Gradle daemon. Use for CI" @@ -75,6 +76,10 @@ fi if builder_start_action build:app; then ./gradlew clean $SAMPLE_FLAGS - builder_finish_action success build:app fi + +if builder_start_action test:app; then + # TODO: define tests + builder_finish_action success test:app +fi diff --git a/android/Tests/KeyboardHarness/build.sh b/android/Tests/KeyboardHarness/build.sh index 5051b1f8b4..c534483e59 100755 --- a/android/Tests/KeyboardHarness/build.sh +++ b/android/Tests/KeyboardHarness/build.sh @@ -28,6 +28,7 @@ builder_describe "Build KeyboardHarness test app for Android." \ "clean" \ "configure" \ "build" \ + "test" \ ":app KeyboardHarness" \ "--ci Don't start the Gradle daemon. Use for CI" @@ -89,6 +90,6 @@ fi if builder_start_action test:app; then echo "TEST_FLAGS $TEST_FLAGS" - + # TODO: define tests builder_finish_action success test:app fi diff --git a/common/linux/build.sh b/common/linux/build.sh new file mode 100755 index 0000000000..26c2cccb18 --- /dev/null +++ b/common/linux/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# Compiles and tests the common Linux modules +# + +# Exit on command failure and when using unset variables: +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# +# TODO: when we have linux-specific tests, add them here +# as child modules +# + +builder_describe "Keyman common Linux modules" \ + clean \ + configure \ + build \ + test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +builder_run_child_actions clean configure build test diff --git a/common/mac/build.sh b/common/mac/build.sh new file mode 100755 index 0000000000..84e3b3761d --- /dev/null +++ b/common/mac/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# Compiles and tests the common mac modules +# + +# Exit on command failure and when using unset variables: +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# +# TODO: when we have mac-specific tests, add them here +# as child modules +# + +builder_describe "Keyman common mac modules" \ + clean \ + configure \ + build \ + test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +builder_run_child_actions clean configure build test diff --git a/common/predictive-text/unit_tests/test.sh b/common/predictive-text/unit_tests/test.sh index bf83160280..74d29cb8ca 100755 --- a/common/predictive-text/unit_tests/test.sh +++ b/common/predictive-text/unit_tests/test.sh @@ -29,6 +29,10 @@ builder_describe "Runs all tests for the language-modeling / predictive-text lay builder_parse "$@" +if builder_has_option --ci && builder_is_debug_build; then + builder_die "Options --ci and --debug are incompatible." +fi + if builder_start_action configure; then verify_npm_setup builder_finish_action success configure @@ -126,10 +130,6 @@ if builder_start_action test:browser; then KARMA_FLAGS="$KARMA_FLAGS --reporters teamcity,BrowserStack" KARMA_CONFIG="CI.conf.cjs" KARMA_INFO_LEVEL="--log-level=debug" - - if builder_is_debug_build; then - echo "$(builder_term --ci) option set; ignoring $(builder_term --debug) option" - fi else KARMA_CONFIG="manual.conf.cjs" if builder_is_debug_build; then diff --git a/common/web/build.sh b/common/web/build.sh new file mode 100755 index 0000000000..0ed50fb521 --- /dev/null +++ b/common/web/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# Compiles and tests the common web modules +# + +# Exit on command failure and when using unset variables: +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# +# TODO: future modules may include +# :lm-message-types \ +# :sentry-manager \ +# + +builder_describe "Keyman common web modules" \ + :keyman-version \ + :types \ + :utils \ + clean \ + configure \ + build \ + test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +builder_run_child_actions clean configure build test diff --git a/common/web/keyman-version/build.sh b/common/web/keyman-version/build.sh index 977ea53ef0..8362ac29ca 100755 --- a/common/web/keyman-version/build.sh +++ b/common/web/keyman-version/build.sh @@ -24,6 +24,7 @@ builder_describe "Build the include script for current Keyman version" \ clean \ build \ publish \ + test \ --dry-run builder_describe_outputs \ diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 6d0d3e0980..80178c10a8 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -108,13 +108,9 @@ export interface LKFlick { export interface LKLayers { /** - * `hardware` or `touch` + * `touch`, or hardware `us`, `iso`, `jis`, `abnt2` */ form?: string; - /** - * `us`, `iso`, `jis`, or `abnt2` - */ - hardware?: string; /** * Minimum width in millimeters */ diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index 7d8d7d113b..983db078f6 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -6,12 +6,16 @@ import { CompilerCallbacks, CompilerEvent } from '../../src/util/compiler-interf import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; -// TODO-LDML: this is largely a port from developer/src/kmc-keyboard/test/helpers/index.ts +// This is related to developer/src/kmc-keyboard/test/helpers/index.ts but has a slightly different API surface +// as this runs at a lower level than the compiler. /** * A CompilerCallbacks implementation for testing */ class TestCompilerCallbacks implements CompilerCallbacks { + loadKpjJsonSchema(): Buffer { + throw new Error('loadKpjJsonSchema not implemented.'); // not needed for this test + } loadLdmlKeyboardTestSchema(): Buffer { return loadLdmlKeyboardTestDataSchema(); } diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index 2573b3ace7..988d2977d3 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -3,11 +3,12 @@ import 'mocha'; import {assert} from 'chai'; import { loadKpjJsonSchema, makePathToFixture } from '../helpers/index.js'; import { KPJFileReader } from "../../src/kpj/kpj-file-reader.js"; -import { KeymanDeveloperProjectType } from '../../src/kpj/keyman-developer-project.js'; +import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/kpj/keyman-developer-project.js'; describe('kpj-file-reader', function () { it('kpj-file-reader should read a valid file', function() { - const path = makePathToFixture('kpj', 'khmer_angkor.kpj'); + const kpjPath = 'khmer_angkor.kpj'; + const path = makePathToFixture('kpj', kpjPath); const input = fs.readFileSync(path); const reader = new KPJFileReader(); const kpj = reader.read(input); @@ -63,7 +64,7 @@ describe('kpj-file-reader', function () { // Test transform of .kpj into a KeymanDeveloperProject - const project = reader.transform(kpj); + const project = reader.transform(kpjPath, kpj); assert.equal(project.options.buildPath, '$PROJECTPATH/build'); assert.isFalse(project.options.checkFilenameConventions); @@ -74,7 +75,7 @@ describe('kpj-file-reader', function () { assert.lengthOf(project.files, 2); - let f = project.files[0]; + let f: KeymanDeveloperProjectFile10 = project.files[0]; console.dir(f); assert.equal(f.id, 'id_f347675c33d2e6b1c705c787fad4941a'); assert.equal(f.filename, 'khmer_angkor.kmn'); @@ -87,7 +88,7 @@ describe('kpj-file-reader', function () { assert.isUndefined(f.details.version); assert.lengthOf(f.childFiles, 1); - f = project.files[1]; + f = project.files[1]; assert.equal(f.id, 'id_8d4eb765f80c9f2b0f769cf4e4aaa456'); assert.equal(f.filename, 'khmer_angkor.kps'); assert.equal(f.filePath, 'source/khmer_angkor.kps'); @@ -99,7 +100,8 @@ describe('kpj-file-reader', function () { assert.isUndefined(f.details.version); assert.lengthOf(f.childFiles, 18); - f = project.files[0].childFiles[0]; + f = project.files[0]; + f = f.childFiles[0]; assert.equal(f.id, 'id_8a1efc7c4ab7cfece8aedd847679ca27'); assert.equal(f.filename, 'khmer_angkor.ico'); assert.equal(f.filePath, 'source/khmer_angkor.ico'); @@ -108,7 +110,8 @@ describe('kpj-file-reader', function () { assert.isEmpty(f.details); assert.lengthOf(f.childFiles, 0); - f = project.files[1].childFiles[0]; + f = project.files[1]; + f = f.childFiles[0]; assert.equal(f.id, 'id_8dc195db32d1fd0514de0ad51fff5df0'); assert.equal(f.filename, 'khmer_angkor.js'); assert.equal(f.filePath, 'source/../build/khmer_angkor.js'); diff --git a/common/web/utils/build.sh b/common/web/utils/build.sh index 3e6769d9aa..f8daffb4a3 100755 --- a/common/web/utils/build.sh +++ b/common/web/utils/build.sh @@ -19,7 +19,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/keyman-version" \ - configure clean build test \ + clean configure build test \ "--ci For use with action ${BUILDER_TERM_START}test${BUILDER_TERM_END} - emits CI-friendly test reports" builder_describe_outputs \ diff --git a/common/windows/build.sh b/common/windows/build.sh new file mode 100755 index 0000000000..480fc95377 --- /dev/null +++ b/common/windows/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# Compiles and tests the common Windows modules +# + +# Exit on command failure and when using unset variables: +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# +# TODO: when we have windows-specific tests, add them here +# as child modules +# + +builder_describe "Keyman common Windows modules" \ + clean \ + configure \ + build \ + test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +builder_run_child_actions clean configure build test diff --git a/common/windows/cpp/include/keymansentry.h b/common/windows/cpp/include/keymansentry.h index bb65c37b5e..4bc3c9e4cf 100644 --- a/common/windows/cpp/include/keymansentry.h +++ b/common/windows/cpp/include/keymansentry.h @@ -1,5 +1,7 @@ #pragma once +#include + /* * Map of Sentry levels for events and breadcrumbs. */ @@ -26,7 +28,7 @@ int keyman_sentry_main(bool is_keyman_developer, const char *logger, int argc, c int keyman_sentry_wmain(bool is_keyman_developer, const char *logger, int argc, wchar_t *argv[], int(*run)(int, wchar_t**)); void keyman_sentry_setexceptionfilter(); -void keyman_sentry_report_message(keyman_sentry_level_t level, const char *message, bool includeStack = false); +sentry_uuid_t keyman_sentry_report_message(keyman_sentry_level_t level, const char *message, bool includeStack = false); void keyman_sentry_report_start(); // @@ -37,3 +39,4 @@ void keyman_sentry_report_start(); // d) privacy options are correctly checked // void keyman_sentry_test_crash(); +void keyman_sentry_test_message(); diff --git a/common/windows/cpp/src/keymansentry.cpp b/common/windows/cpp/src/keymansentry.cpp index ce287723e1..71f640a4a5 100644 --- a/common/windows/cpp/src/keymansentry.cpp +++ b/common/windows/cpp/src/keymansentry.cpp @@ -32,7 +32,7 @@ int keyman_sentry_init(bool is_keyman_developer, const char *logger) { // Set the sentry-db-directory to a writeable location - char szPath[MAX_PATH + 64]; // sufficient length for sentry-0.4.9-db etc + char szPath[MAX_PATH + 64]; // sufficient length for sentry-0.6.0-db etc LPITEMIDLIST pidl; if (SUCCEEDED(SHGetFolderLocation(0, CSIDL_LOCAL_APPDATA, NULL, 0, &pidl))) { @@ -43,7 +43,7 @@ int keyman_sentry_init(bool is_keyman_developer, const char *logger) { *p = 0; } - strcat_s(szPath, "sentry-0.4.9-db"); + strcat_s(szPath, "sentry-" SENTRY_SDK_VERSION "-db"); sentry_options_set_database_path(options, szPath); } ILFree(pidl); @@ -132,7 +132,7 @@ sentry_value_t CaptureStackTrace(PVOID TopAddr, DWORD FramesToSkip) { void keyman_sentry_report_exception(DWORD ExceptionCode, PVOID ExceptionAddress) { sentry_value_t event; const int FRAMES_TO_SKIP = 0; - + sentry_uuid_t uuid = { 0 }; char message[64]; wsprintfA(message, "Exception %x at %p", (unsigned int) ExceptionCode, ExceptionAddress); @@ -159,18 +159,22 @@ void keyman_sentry_report_exception(DWORD ExceptionCode, PVOID ExceptionAddress) sentry_value_set_by_key(event, "threads", threads); } - sentry_capture_event(event); + uuid = sentry_capture_event(event); } fputs(message, stderr); fputs("\n", stderr); if(g_report_exceptions) { fputs("This error has been automatically reported to the Keyman team.\n", stderr); + char uuid_string[37]; + sentry_uuid_as_string(&uuid, uuid_string); + fprintf(stderr, "Sentry uuid: %s\n", uuid_string); } } -void keyman_sentry_report_message(keyman_sentry_level_t level, const char *message, bool includeStack) { +sentry_uuid_t keyman_sentry_report_message(keyman_sentry_level_t level, const char *message, bool includeStack) { const int FRAMES_TO_SKIP = 0; + sentry_uuid_t uuid = { 0 }; if ((g_report_exceptions && (level == SENTRY_LEVEL_ERROR || level == SENTRY_LEVEL_DEBUG)) || g_report_messages) { sentry_value_t event; @@ -190,12 +194,15 @@ void keyman_sentry_report_message(keyman_sentry_level_t level, const char *messa } } - sentry_capture_event(event); + uuid = sentry_capture_event(event); } + + return uuid; } /* Wrappers for main, wmain */ +#define SENTRY_USE_LOCAL_FILTER #ifdef SENTRY_USE_LOCAL_FILTER LPTOP_LEVEL_EXCEPTION_FILTER LastFilter; @@ -236,6 +243,11 @@ int keyman_sentry_main(bool is_keyman_developer, const char *logger, int argc, c keyman_sentry_test_crash(); } + if (argc > 1 && !strcmp(argv[1], "-sentry-client-test-message")) { + // Undocumented test parameter + keyman_sentry_test_message(); + } + int res = run(argc, argv); keyman_sentry_shutdown(); @@ -253,6 +265,11 @@ int keyman_sentry_wmain(bool is_keyman_developer, const char *logger, int argc, keyman_sentry_test_crash(); } + if (argc > 1 && !wcscmp(argv[1], L"-sentry-client-test-message")) { + // Undocumented test parameter + keyman_sentry_test_message(); + } + int res = run(argc, argv); keyman_sentry_shutdown(); @@ -260,9 +277,19 @@ int keyman_sentry_wmain(bool is_keyman_developer, const char *logger, int argc, return res; } +void keyman_sentry_test_message() { + fputs("Testing Sentry reporting:\n", stderr); + auto uuid = keyman_sentry_report_message(KEYMAN_SENTRY_LEVEL_INFO, "Testing Sentry message reporting", true); + char uuid_string[37]; + sentry_uuid_as_string(&uuid, uuid_string); + fprintf(stderr, "Sentry uuid: %s\n", uuid_string); + exit(0); +} void keyman_sentry_test_crash() { - fputs("Testing exception reporting:\n", stderr); + fputs("Testing Sentry exception reporting:\n", stderr); RaiseException(0x0EA0BEEF, EXCEPTION_NONCONTINUABLE, 0, NULL); + fputs("Should not have gotten here\n", stderr); + exit(1); } /* Delay load sentry.dll from our subdirectory: #5166 */ @@ -274,12 +301,12 @@ void keyman_sentry_test_crash() { #else #define SENTRY_DLL SENTRY_BASE_DLL #endif -#define SENTRY_INSTALL_PATH "sentry-0.4.9\\" SENTRY_DLL +#define SENTRY_INSTALL_PATH "sentry-" SENTRY_SDK_VERSION "\\" SENTRY_DLL #define SENTRY_DEV_PATH "common\\windows\\delphi\\ext\\sentry\\" SENTRY_DLL #define ENV_KEYMAN_ROOT "KEYMAN_ROOT" HMODULE LoadSentryLibrary() { - //MAX_PATH + 64 chars leaves space for "\sentry-0.4.9\sentry.x64.dll" + //MAX_PATH + 64 chars leaves space for "\sentry-0.6.0\sentry.x64.dll" char buf[MAX_PATH + 64], keyman_root[MAX_PATH + 64]; int nsize = GetModuleFileNameA(0, buf, MAX_PATH); diff --git a/common/windows/delphi/ext/sentry.txt b/common/windows/delphi/ext/sentry.txt index 2463cd98f3..29c5273516 100644 --- a/common/windows/delphi/ext/sentry.txt +++ b/common/windows/delphi/ext/sentry.txt @@ -4,7 +4,7 @@ Keyman Third Party Package Readme: Sentry Name: Sentry Purpose: Sentry provides error reporting to a Sentry server Source: https://github.com/getsentry/sentry-native -Version: 0.4.9 +Version: 0.6.0 License: MIT Updated for: N/A Documentation: https://docs.sentry.io/platforms/native/ @@ -15,8 +15,8 @@ Include Paths: N/A Installation notes ------------------ -The original C++ version can be downloaded from site above. Source is -not included +The original C++ version can be downloaded from site above, and must be +built following the instructions below. Build notes ----------- @@ -27,6 +27,9 @@ team. This needs to be updated whenever Sentry is updated. Sentry.Client.pas is a wrapper of sentry.pas which provides a Delphi-style interface to Sentry. +Note: as of 0.6.0, performance SDKs are not included in Sentry.Client.pas, +because we are not currently using them. + build_sentry_for_keyman.bat should be copied into the sentry-native repo, and tweaked as required for updated versions of Sentry. Run: @@ -37,6 +40,11 @@ and tweaked as required for updated versions of Sentry. Run: install: prepare install folder in sentry-native deploy: copy the sentry.dll and related files into this repo +**Important:** ensure that the sentry folder name in all .wxs files is +updated to match the SENTRY_SDK_VERSION from sentry.h. Source C++ and .pas +files use the constant to refer to the location where the files may be +found. + License - MIT ------------- diff --git a/common/windows/delphi/ext/sentry/crashpad_handler.exe b/common/windows/delphi/ext/sentry/crashpad_handler.exe index 41e10b88e1..76276e6bec 100644 Binary files a/common/windows/delphi/ext/sentry/crashpad_handler.exe and b/common/windows/delphi/ext/sentry/crashpad_handler.exe differ diff --git a/common/windows/delphi/ext/sentry/sentry.dll b/common/windows/delphi/ext/sentry/sentry.dll index 96d753e4cc..86fbd12db6 100644 Binary files a/common/windows/delphi/ext/sentry/sentry.dll and b/common/windows/delphi/ext/sentry/sentry.dll differ diff --git a/common/windows/delphi/ext/sentry/sentry.h b/common/windows/delphi/ext/sentry/sentry.h index 91146520a9..b5381642da 100644 --- a/common/windows/delphi/ext/sentry/sentry.h +++ b/common/windows/delphi/ext/sentry/sentry.h @@ -23,8 +23,14 @@ extern "C" { #endif /* SDK Version */ -#define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.9" +#ifndef SENTRY_SDK_NAME +# ifdef __ANDROID__ +# define SENTRY_SDK_NAME "sentry.native.android" +# else +# define SENTRY_SDK_NAME "sentry.native" +# endif +#endif +#define SENTRY_SDK_VERSION "0.6.0" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ @@ -46,6 +52,10 @@ extern "C" { #elif defined(__linux) || defined(__linux__) # define SENTRY_PLATFORM_LINUX # define SENTRY_PLATFORM_UNIX +#elif defined(_AIX) +/* IBM i PASE is also counted as AIX */ +# define SENTRY_PLATFORM_AIX +# define SENTRY_PLATFORM_UNIX #else # error unsupported platform #endif @@ -189,7 +199,7 @@ SENTRY_API sentry_value_t sentry_value_new_int32(int32_t value); SENTRY_API sentry_value_t sentry_value_new_double(double value); /** - * Creates a new boolen value. + * Creates a new boolean value. */ SENTRY_API sentry_value_t sentry_value_new_bool(int value); @@ -399,8 +409,8 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( * * See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ * - * The returned object needs to be attached to either an exception - * event, or a thread object. + * The returned object must be attached to either an exception or thread + * object. * * If `ips` is NULL the current stack trace is captured, otherwise `len` * stack trace instruction pointers are attached to the event. @@ -408,6 +418,17 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_stacktrace( void **ips, size_t len); +/** + * Sets the Stack Trace conforming to the Stack Trace Interface in a value. + * + * The value argument must be either an exception or thread object. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` stack + * trace instruction pointers are attached to the event. + */ +SENTRY_EXPERIMENTAL_API void sentry_value_set_stacktrace( + sentry_value_t value, void **ips, size_t len); + /** * Adds an Exception to an Event value. * @@ -469,9 +490,12 @@ typedef struct sentry_ucontext_s { * * If the address is given in `addr` the stack is unwound form there. * Otherwise (NULL is passed) the current instruction pointer is used as - * start address. The stack trace is written to `stacktrace_out` with up to - * `max_len` frames being written. The actual number of unwound stackframes - * is returned. + * start address. + * Unwinding with a given `addr` is not supported on all platforms. + * + * The stack trace in the form of instruction-addresses, is written to the + * caller allocated `stacktrace_out`, with up to `max_len` frames being written. + * The actual number of unwound stackframes is returned. */ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack( void *addr, void **stacktrace_out, size_t max_len); @@ -479,8 +503,12 @@ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack( /** * Unwinds the stack from the given context. * - * The stack trace is written to `stacktrace_out` with up to `max_len` frames - * being written. The actual number of unwound stackframes is returned. + * The caller is responsible to construct an appropriate `sentry_ucontext_t`. + * Unwinding from a user context is not supported on all platforms. + * + * The stack trace in the form of instruction-addresses, is written to the + * caller allocated `stacktrace_out`, with up to `max_len` frames being written. + * The actual number of unwound stackframes is returned. */ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack_from_ucontext( const sentry_ucontext_t *uctx, void **stacktrace_out, size_t max_len); @@ -543,13 +571,21 @@ typedef struct sentry_envelope_s sentry_envelope_t; SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope); /** - * Given an envelope returns the embedded event if there is one. + * Given an Envelope, returns the embedded Event if there is one. * - * This returns a borrowed value to the event in the envelope. + * This returns a borrowed value to the Event in the Envelope. */ SENTRY_API sentry_value_t sentry_envelope_get_event( const sentry_envelope_t *envelope); +/** + * Given an Envelope, returns the embedded Transaction if there is one. + * + * This returns a borrowed value to the Transaction in the Envelope. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_envelope_get_transaction( + const sentry_envelope_t *envelope); + /** * Serializes the envelope. * @@ -571,7 +607,7 @@ SENTRY_API int sentry_envelope_write_to_file( /** * The Sentry Client Options. * - * See https://docs.sentry.io/error-reporting/configuration/ + * See https://docs.sentry.io/platforms/native/configuration/ */ struct sentry_options_s; typedef struct sentry_options_s sentry_options_t; @@ -595,13 +631,16 @@ typedef struct sentry_options_s sentry_options_t; * * `startup_func`: This hook will be called by sentry inside of `sentry_init` * and instructs the transport to initialize itself. Failures will bubble up * to `sentry_init`. + * * `flush_func`: Instructs the transport to flush its queue. + * This hook receives a millisecond-resolution `timeout` parameter and should + * return `0` if the transport queue is flushed within the timeout. * * `shutdown_func`: Instructs the transport to flush its queue and shut down. * This hook receives a millisecond-resolution `timeout` parameter and should - * return `true` when the transport was flushed and shut down successfully. - * In case of `false`, sentry will log an error, but continue with freeing the - * transport. + * return `0` if the transport is flushed and shut down successfully. + * In case of a non-zero return value, sentry will log an error, but continue + * with freeing the transport. * * `free_func`: Frees the transports `state`. This hook might be called even - * though `shutdown_func` returned `false` previously. + * though `shutdown_func` returned a failure code previously. * * The transport interface might be extended in the future with hooks to flush * its internal queue without shutting down, and to dump its internal queue to @@ -641,6 +680,16 @@ SENTRY_API void sentry_transport_set_free_func( SENTRY_API void sentry_transport_set_startup_func(sentry_transport_t *transport, int (*startup_func)(const sentry_options_t *options, void *state)); +/** + * Sets the transport flush hook. + * + * This hook will receive a millisecond-resolution timeout. + * It should return `0` if all the pending envelopes are + * sent within the timeout, or `1` if the timeout is hit. + */ +SENTRY_API void sentry_transport_set_flush_func(sentry_transport_t *transport, + int (*flush_func)(uint64_t timeout, void *state)); + /** * Sets the transport shutdown hook. * @@ -671,6 +720,20 @@ SENTRY_API void sentry_transport_free(sentry_transport_t *transport); SENTRY_API sentry_transport_t *sentry_new_function_transport( void (*func)(const sentry_envelope_t *envelope, void *data), void *data); +/** + * This represents an interface for user-defined backends. + * + * Backends are responsible to handle crashes. They are maintained at runtime + * via various life-cycle hooks from the sentry-core. + * + * At this point none of those interfaces are exposed in the API including + * creation and destruction. The main use-case of the backend in the API at this + * point is to disable it via `sentry_options_set_backend` at runtime before it + * is initialized. + */ +struct sentry_backend_s; +typedef struct sentry_backend_s sentry_backend_t; + /* -- Options APIs -- */ /** @@ -707,11 +770,28 @@ SENTRY_API void sentry_options_set_transport( * call `sentry_value_decref` on the provided event, and return a * `sentry_value_new_null()` instead. * + * If you have set an `on_crash` callback (independent of whether it discards or + * retains the event), `before_send` will no longer be invoked for crash-events, + * which allows you to better distinguish between crashes and all other events + * in client-side pre-processing. + * * This function may be invoked inside of a signal handler and must be safe for * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see * the documentation on SEH (structured exception handling) for more information * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Up to version 0.4.18 the `before_send` callback wasn't invoked in case the + * event sampling discarded an event. In the current implementation the + * `before_send` callback is invoked even if the event sampling discards the + * event, following the cross-SDK session filter order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * On Windows the crashpad backend can capture fast-fail crashes which by-pass + * SEH. Since the `before_send` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. */ typedef sentry_value_t (*sentry_event_function_t)( sentry_value_t event, void *hint, void *closure); @@ -724,6 +804,64 @@ typedef sentry_value_t (*sentry_event_function_t)( SENTRY_API void sentry_options_set_before_send( sentry_options_t *opts, sentry_event_function_t func, void *data); +/** + * Type of the `on_crash` callback. + * + * The `on_crash` callback replaces the `before_send` callback for crash events. + * The interface is analogous to `before_send` in that the callback takes + * ownership of the `event`, and should usually return that same event. In case + * the event should be discarded, the callback needs to call + * `sentry_value_decref` on the provided event, and return a + * `sentry_value_new_null()` instead. + * + * Only the `inproc` backend currently fills the passed-in event with useful + * data and processes any modifications to the return value. Since both + * `breakpad` and `crashpad` use minidumps to capture the crash state, the + * passed-in event is empty when using these backends, and they ignore any + * changes to the return value. + * + * If you set this callback in the options, it prevents a concurrently enabled + * `before_send` callback from being invoked in the crash case. This allows for + * better differentiation between crashes and other events and gradual migration + * from existing `before_send` implementations: + * + * - if you have a `before_send` implementation and do not define an `on_crash` + * callback your application will receive both normal and crash events as + * before + * - if you have a `before_send` implementation but only want to handle normal + * events with it, then you can define an `on_crash` callback that returns + * the passed-in event and does nothing else + * - if you are not interested in normal events, but only want to act on + * crashes (within the limits mentioned below), then only define an + * `on_crash` callback with the option to filter (on all backends) or enrich + * (only inproc) the crash event + * + * This function may be invoked inside of a signal handler and must be safe for + * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. + * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see + * the documentation on SEH (structured exception handling) for more information + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Platform-specific behavior: + * + * - does not work with crashpad on macOS. + * - for breakpad on Linux the `uctx` parameter is always NULL. + * - on Windows the crashpad backend can capture fast-fail crashes which + * by-pass SEH. Since `on_crash` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. + */ +typedef sentry_value_t (*sentry_crash_function_t)( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure); + +/** + * Sets the `on_crash` callback. + * + * See the `sentry_crash_function_t` typedef above for more information. + */ +SENTRY_API void sentry_options_set_on_crash( + sentry_options_t *opts, sentry_crash_function_t func, void *data); + /** * Sets the DSN. */ @@ -738,6 +876,19 @@ SENTRY_API const char *sentry_options_get_dsn(const sentry_options_t *opts); * Sets the sample rate, which should be a double between `0.0` and `1.0`. * Sentry will randomly discard any event that is captured using * `sentry_capture_event` when a sample rate < 1 is set. + * + * The sampling happens at the end of the event processing according to the + * following order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * Only items 3. to 6. are currently applicable to sentry-native. This means + * each processing step is executed even if the sampling discards the event + * before sending it to the backend. This is particularly relevant to users of + * the `before_send` callback. + * + * The above is in contrast to versions up to 0.4.18 where the sampling happened + * at the beginning of the processing/filter sequence. */ SENTRY_API void sentry_options_set_sample_rate( sentry_options_t *opts, double sample_rate); @@ -999,6 +1150,29 @@ SENTRY_API void sentry_options_set_database_pathw( SENTRY_API void sentry_options_set_system_crash_reporter_enabled( sentry_options_t *opts, int enabled); +/** + * Sets the maximum time (in milliseconds) to wait for the asynchronous tasks to + * end on shutdown, before attempting a forced termination. + */ +SENTRY_API void sentry_options_set_shutdown_timeout( + sentry_options_t *opts, uint64_t shutdown_timeout); + +/** + * Gets the maximum time (in milliseconds) to wait for the asynchronous tasks to + * end on shutdown, before attempting a forced termination. + */ +SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); + +/** + * Sets a user-defined backend. + * + * Since creation and destruction of backends is not exposed in the API, this + * can only be used to set the backend to `NULL`, which disables the backend in + * the initialization. + */ +SENTRY_API void sentry_options_set_backend( + sentry_options_t *opts, sentry_backend_t *backend); + /* -- Global APIs -- */ /** @@ -1012,6 +1186,15 @@ SENTRY_API void sentry_options_set_system_crash_reporter_enabled( */ SENTRY_API int sentry_init(sentry_options_t *options); +/** + * Instructs the transport to flush its send queue. + * + * The `timeout` parameter is in milliseconds. + * + * Returns 0 on success, or a non-zero return value in case the timeout is hit. + */ +SENTRY_API int sentry_flush(uint64_t timeout); + /** * Shuts down the sentry client and forces transports to flush out. * @@ -1080,6 +1263,10 @@ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); /** * Sends a sentry event. + * + * If returns a nil UUID if the event being passed in is a transaction, and the + * transaction will not be sent nor consumed. `sentry_transaction_finish` should + * be used to send transactions. */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); @@ -1154,16 +1341,48 @@ SENTRY_API void sentry_remove_fingerprint(void); */ SENTRY_API void sentry_set_transaction(const char *transaction); -/** - * Removes the transaction. - */ -SENTRY_API void sentry_remove_transaction(void); - /** * Sets the event level. */ SENTRY_API void sentry_set_level(sentry_level_t level); +/** + * Sets the maximum number of spans that can be attached to a + * transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_set_max_spans( + sentry_options_t *opts, size_t max_spans); + +/** + * Gets the maximum number of spans that can be attached to a + * transaction. + */ +SENTRY_EXPERIMENTAL_API size_t sentry_options_get_max_spans( + sentry_options_t *opts); + +/** + * Sets the sample rate for transactions. Should be a double between + * `0.0` and `1.0`. Transactions will be randomly discarded during + * `sentry_transaction_finish` when the sample rate is < 1.0. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sample_rate( + sentry_options_t *opts, double sample_rate); + +/** + * Returns the sample rate for transactions. + */ +SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( + sentry_options_t *opts); + +/* -- Session APIs -- */ + +typedef enum { + SENTRY_SESSION_STATUS_OK, + SENTRY_SESSION_STATUS_CRASHED, + SENTRY_SESSION_STATUS_ABNORMAL, + SENTRY_SESSION_STATUS_EXITED, +} sentry_session_status_t; + /** * Starts a new session. */ @@ -1174,6 +1393,505 @@ SENTRY_API void sentry_start_session(void); */ SENTRY_API void sentry_end_session(void); +/** + * Ends a session with an explicit `status` code. + */ +SENTRY_EXPERIMENTAL_API void sentry_end_session_with_status( + sentry_session_status_t status); + +/* -- Performance Monitoring/Tracing APIs -- */ + +/** + * A sentry Transaction Context. + * + * See Transaction Interface under + * https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes + */ +struct sentry_transaction_context_s; +typedef struct sentry_transaction_context_s sentry_transaction_context_t; + +/** + * A sentry Transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/transaction/ + */ +struct sentry_transaction_s; +typedef struct sentry_transaction_s sentry_transaction_t; + +/** + * A sentry Span. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ + */ +struct sentry_span_s; +typedef struct sentry_span_s sentry_span_t; + +/** + * Constructs a new Transaction Context. The returned value needs to be passed + * into `sentry_transaction_start` in order to be recorded and sent to sentry. + * + * See + * https://docs.sentry.io/platforms/native/enriching-events/transaction-name/ + * for an explanation of a Transaction's `name`, and + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around an `operation`'s value. + * + * Also see https://develop.sentry.dev/sdk/event-payloads/transaction/#anatomy + * for an explanation of `operation`, in addition to other properties and + * actions that can be performed on a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction Context + * if it needs to be mutated across threads. Methods operating on the + * Transaction Context will mention what kind of expectations they carry if they + * need to mutate or access the object in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * +sentry_transaction_context_new(const char *name, const char *operation); + +/** + * Sets the `name` on a Transaction Context, which will be used in the + * Transaction constructed off of the context. + * + * The Transaction Context should not be mutated by other functions while + * setting a name on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( + sentry_transaction_context_t *tx_cxt, const char *name); + +/** + * Sets the `operation` on a Transaction Context, which will be used in the + * Transaction constructed off of the context + * + * See https://develop.sentry.dev/sdk/performance/span-operations/ for + * conventions on `operation`s. + * + * The Transaction Context should not be mutated by other functions while + * setting an operation on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( + sentry_transaction_context_t *tx_cxt, const char *operation); + +/** + * Sets the `sampled` field on a Transaction Context, which will be used in the + * Transaction constructed off of the context. + * + * When passed any value above 0, the Transaction will bypass all sampling + * options and always be sent to sentry. If passed 0, this Transaction and its + * child spans will never be sent to sentry. + * + * The Transaction Context should not be mutated by other functions while + * setting `sampled` on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( + sentry_transaction_context_t *tx_cxt, int sampled); + +/** + * Removes the `sampled` field on a Transaction Context, which will be used in + * the Transaction constructed off of the context. + * + * The Transaction will use the sampling rate as defined in `sentry_options`. + * + * The Transaction Context should not be mutated by other functions while + * removing `sampled`. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( + sentry_transaction_context_t *tx_cxt); + +/** + * Update the Transaction Context with the given HTTP header key/value pair. + * + * This is used to propagate distributed tracing metadata from upstream + * services. Therefore, the headers of incoming requests should be fed into this + * function so that sentry is able to continue a trace that was started by an + * upstream service. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( + sentry_transaction_context_t *tx_cxt, const char *key, const char *value); + +/** + * Starts a new Transaction based on the provided context, restored from an + * external integration (i.e. a span from a different SDK) or manually + * constructed by a user. + * + * The second parameter is a custom Sampling Context to be used with a Traces + * Sampler to make a more informed sampling decision. The SDK does not currently + * support a custom Traces Sampler and this parameter is ignored for the time + * being but needs to be provided. + * + * Returns a Transaction, which is expected to be manually managed by the + * caller. Manual management involves ensuring that `sentry_transaction_finish` + * is invoked for the Transaction, and that the caller manually starts and + * finishes any child Spans as needed on the Transaction. + * + * Not invoking `sentry_transaction_finish` with the returned Transaction means + * it will be discarded, and will not be sent to sentry. + * + * To ensure that any Events or Message Events are associated with this + * Transaction while it is active, invoke and pass in the Transaction returned + * by this function to `sentry_set_transaction_object`. Further documentation on + * this can be found in `sentry_set_transaction_object`'s docstring. + * + * Takes ownership of `transaction_context`. A Transaction Context cannot be + * modified or re-used after it is used to start a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction if it + * needs to be mutated across threads. Methods operating on the Transaction will + * mention what kind of expectations they carry if they need to mutate or access + * the object in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( + sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx); + +/** + * Finishes and sends a Transaction to sentry. The event ID of the Transaction + * will be returned if this was successful; A nil UUID will be returned + * otherwise. + * + * Always takes ownership of `transaction`, regardless of whether the operation + * was successful or not. A Transaction cannot be modified or re-used after it + * is finished. + */ +SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( + sentry_transaction_t *tx); + +/** + * Sets the Transaction so any Events sent while the Transaction + * is active will be associated with the Transaction. + * + * If the Transaction being passed in is unsampled, it will still be associated + * with any new Events. This will lead to some Events pointing to orphan or + * missing traces in sentry, see + * https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#orphan-traces-and-broken-subtraces + * + * This increases the number of references pointing to the Transaction. Invoke + * `sentry_transaction_finish` to remove the Transaction set by this function as + * well as its reference by passing in the same Transaction as the one passed + * into this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_transaction_object( + sentry_transaction_t *tx); + +/** + * Sets the Span so any Events sent while the Span + * is active will be associated with the Span. + * + * This increases the number of references pointing to the Span. Invoke + * `sentry_span_finish` to remove the Span set by this function as well + * as its reference by passing in the same Span as the one passed into + * this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_span_t *span); + +/** + * Starts a new Span. + * + * The return value of `sentry_transaction_start` should be passed in as + * `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * + * This increases the number of references pointing to the Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( + sentry_transaction_t *parent, char *operation, char *description); + +/** + * Starts a new Span. + * + * The return value of `sentry_span_start_child` may be passed in as `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. + */ +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( + sentry_span_t *parent, char *operation, char *description); + +/** + * Finishes a Span. + * + * This takes ownership of `span`. A Span cannot be modified or re-used after it + * is finished. + * + * This will mutate the `span`'s containing Transaction, so the containing + * Transaction should also not be mutated by other functions when finishing a + * span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_finish(sentry_span_t *span); + +/** + * Sets a tag on a Transaction to the given string value. + * + * Tags longer than 200 bytes will be truncated. + * + * The Transaction should not be mutated by other functions while a tag is being + * set on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( + sentry_transaction_t *transaction, const char *tag, const char *value); + +/** + * Removes a tag from a Transaction. + * + * The Transaction should not be mutated by other functions while a tag is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( + sentry_transaction_t *transaction, const char *tag); + +/** + * Sets the given key in a Transaction's "data" section to the given value. + * + * The Transaction should not be mutated by other functions while data is being + * set on it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( + sentry_transaction_t *transaction, const char *key, sentry_value_t value); + +/** + * Removes a key from a Transaction's "data" section. + * + * The Transaction should not be mutated by other functions while data is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( + sentry_transaction_t *transaction, const char *key); + +/** + * Sets a tag on a Span to the given string value. + * + * Tags longer than 200 bytes will be truncated. + * + * The Span should not be mutated by other functions while a tag is being set on + * it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( + sentry_span_t *span, const char *tag, const char *value); + +/** + * Removes a tag from a Span. + * + * The Span should not be mutated by other functions while a tag is being + * removed from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( + sentry_span_t *span, const char *tag); + +/** + * Sets the given key in a Span's "data" section to the given value. + * + * The Span should not be mutated by other functions while data is being set on + * it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_data( + sentry_span_t *span, const char *key, sentry_value_t value); + +/** + * Removes a key from a Span's "data" section. + * + * The Span should not be mutated by other functions while data is being removed + * from it. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( + sentry_span_t *span, const char *key); + +/** + * Sets a Transaction's name. + * + * The Transaction should not be mutated by other functions while setting its + * name. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( + sentry_transaction_t *transaction, const char *name); + +/** + * The status of a Span or Transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for documentation. + */ +typedef enum { + // The operation completed successfully. + // HTTP status 100..299 + successful redirects from the 3xx range. + SENTRY_SPAN_STATUS_OK, + // The operation was cancelled (typically by the user). + SENTRY_SPAN_STATUS_CANCELLED, + // Unknown. Any non-standard HTTP status code. + // "We do not know whether the transaction failed or succeeded." + SENTRY_SPAN_STATUS_UNKNOWN, + // Client specified an invalid argument. 4xx. + // Note that this differs from FailedPrecondition. InvalidArgument + // indicates arguments that are problematic regardless of the + // state of the system. + SENTRY_SPAN_STATUS_INVALID_ARGUMENT, + // Deadline expired before operation could complete. + // For operations that change the state of the system, this error may be + // returned even if the operation has been completed successfully. + // HTTP redirect loops and 504 Gateway Timeout. + SENTRY_SPAN_STATUS_DEADLINE_EXCEEDED, + // 404 Not Found. Some requested entity (file or directory) was not found. + SENTRY_SPAN_STATUS_NOT_FOUND, + // Already exists (409) + // Some entity that we attempted to create already exists. + SENTRY_SPAN_STATUS_ALREADY_EXISTS, + // 403 Forbidden + // The caller does not have permission to execute the specified operation. + SENTRY_SPAN_STATUS_PERMISSION_DENIED, + // 429 Too Many Requests + // Some resource has been exhausted, perhaps a per-user quota or perhaps + // the entire file system is out of space. + SENTRY_SPAN_STATUS_RESOURCE_EXHAUSTED, + // Operation was rejected because the system is not in a state required for + // the operation's execution. + SENTRY_SPAN_STATUS_FAILED_PRECONDITION, + // The operation was aborted, typically due to a concurrency issue. + SENTRY_SPAN_STATUS_ABORTED, + // Operation was attempted past the valid range. + SENTRY_SPAN_STATUS_OUT_OF_RANGE, + // 501 Not Implemented + // Operation is not implemented or not enabled. + SENTRY_SPAN_STATUS_UNIMPLEMENTED, + // Other/generic 5xx + SENTRY_SPAN_STATUS_INTERNAL_ERROR, + // 503 Service Unavailable + SENTRY_SPAN_STATUS_UNAVAILABLE, + // Unrecoverable data loss or corruption + SENTRY_SPAN_STATUS_DATA_LOSS, + // 401 Unauthorized (actually does mean unauthenticated according to RFC + // 7235) + // Prefer PermissionDenied if a user is logged in. + SENTRY_SPAN_STATUS_UNAUTHENTICATED, +} sentry_span_status_t; + +/** + * Sets a Span's status. + * + * The Span should not be mutated by other functions while setting its status. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_status( + sentry_span_t *span, sentry_span_status_t status); + +/** + * Sets a Transaction's status. + * + * The Transaction should not be mutated by other functions while setting its + * status. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status( + sentry_transaction_t *tx, sentry_span_status_t status); + +/** + * Type of the `iter_headers` callback. + * + * The callback is being called with HTTP header key/value pairs. + * These headers can be attached to outgoing HTTP requests to propagate + * distributed tracing metadata to downstream services. + * + */ +typedef void (*sentry_iter_headers_function_t)( + const char *key, const char *value, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_iter_headers(sentry_span_t *span, + sentry_iter_headers_function_t callback, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( + sentry_transaction_t *tx, sentry_iter_headers_function_t callback, + void *userdata); + +/** + * Returns whether the application has crashed on the last run. + * + * Notes: + * * The underlying value is set by sentry_init() - it must be called first. + * * Call sentry_clear_crashed_last_run() to reset for the next app run. + * + * Possible return values: + * 1 = the last run was a crash + * 0 = no crash recognized + * -1 = sentry_init() hasn't been called yet + */ +SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(void); + +/** + * Clear the status of the "crashed-last-run". You should explicitly call + * this after sentry_init() if you're using sentry_get_crashed_last_run(). + * Otherwise, the same information is reported on any subsequent runs. + * + * Notes: + * * This doesn't change the value of sentry_get_crashed_last_run() yet. + * However, if sentry_init() is called again, the value will change. + * * This may only be called after sentry_init() and before sentry_close(). + * + * Returns 0 on success, 1 on error. + */ +SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(void); + +/** + * Sentry SDK version. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(void); + +/** + * Sentry SDK name. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(void); + +/** + * Sentry SDK User-Agent. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(void); + #ifdef __cplusplus } #endif diff --git a/common/windows/delphi/ext/sentry/sentry.lib b/common/windows/delphi/ext/sentry/sentry.lib index f9c6b6671d..9858e8c039 100644 Binary files a/common/windows/delphi/ext/sentry/sentry.lib and b/common/windows/delphi/ext/sentry/sentry.lib differ diff --git a/common/windows/delphi/ext/sentry/sentry.pas b/common/windows/delphi/ext/sentry/sentry.pas index 24e85c9b9a..af47eb383f 100644 --- a/common/windows/delphi/ext/sentry/sentry.pas +++ b/common/windows/delphi/ext/sentry/sentry.pas @@ -1,35 +1,35 @@ -{$D-} // Don't include debug information -// Delphi translation of sentry.h -// Sentry Native API 0.4.9 -// https://github.com/getsentry/sentry-native -unit sentry; - -{ - * NOTE on encodings: - * - * Sentry will assume an encoding of UTF-8 for all string data that is captured - * and being sent to sentry as an Event. - * All the functions that are dealing with *paths* will assume an OS-specific - * encoding, typically ANSI on Windows, UTF-8 macOS, and the locale encoding on - * Linux; and they provide wchar-compatible alternatives on Windows which are - * preferred. -} - -interface - -{$DEFINE SENTRY_API} - -//{$ALIGN ON} -{$MINENUMSIZE 4} - -{$WARN SYMBOL_PLATFORM OFF} - -{$IF DEFINED(MSWINDOWS)} - -uses - Winapi.Windows; - -{$DEFINE SENTRY_UUID_WINDOWS} +{$D-} // Don't include debug information +// Delphi translation of sentry.h +// Sentry Native API 0.6.0 - ***excluding performance APIs*** +// https://github.com/getsentry/sentry-native +unit sentry; + +{ + * NOTE on encodings: + * + * Sentry will assume an encoding of UTF-8 for all string data that is captured + * and being sent to sentry as an Event. + * All the functions that are dealing with *paths* will assume an OS-specific + * encoding, typically ANSI on Windows, UTF-8 macOS, and the locale encoding on + * Linux; and they provide wchar-compatible alternatives on Windows which are + * preferred. +} + +interface + +{$DEFINE SENTRY_API} + +//{$ALIGN ON} +{$MINENUMSIZE 4} + +{$WARN SYMBOL_PLATFORM OFF} + +{$IF DEFINED(MSWINDOWS)} + +uses + Winapi.Windows; + +{$DEFINE SENTRY_UUID_WINDOWS} {$ELSEIF DEFINED(ANDROID)} @@ -45,15 +45,15 @@ uses {$ENDIF} const -{$IFDEF WIN64} +{$IFDEF WIN64} sentry_dll = 'sentry.x64.dll'; {$ELSE} sentry_dll = 'sentry.dll'; {$ENDIF} - + const SENTRY_SDK_NAME = 'sentry.native'; - SENTRY_SDK_VERSION = '0.4.9'; + SENTRY_SDK_VERSION = '0.6.0'; SENTRY_SDK_USER_AGENT = SENTRY_SDK_NAME + '/' + SENTRY_SDK_VERSION; {$IF DEFINED(MSWINDOWS)} @@ -62,32 +62,32 @@ const {$MESSAGE ERROR 'Unsupported platform'} {$ENDIF} -type - size_t = NativeUInt; - -// The library internally uses the system malloc and free functions to manage -// memory. It does not use realloc. The reason for this is that on unix -// platforms we fall back to a simplistic page allocator once we have -// encountered a SIGSEGV or other terminating signal as malloc is no longer -// safe to use. Since we cannot portably reallocate allocations made on the -// pre-existing allocator we're instead not using realloc. -// -// Note also that after SIGSEGV sentry_free() becomes a noop. - -// -// allocates memory with the underlying allocator -// -function sentry_malloc(size: size_t): Pointer; cdecl; external sentry_dll delayed; - -// -// releases memory allocated from the underlying allocator -procedure sentry_free(ptr: Pointer); cdecl; external sentry_dll delayed; - +type + size_t = NativeUInt; + +// The library internally uses the system malloc and free functions to manage +// memory. It does not use realloc. The reason for this is that on unix +// platforms we fall back to a simplistic page allocator once we have +// encountered a SIGSEGV or other terminating signal as malloc is no longer +// safe to use. Since we cannot portably reallocate allocations made on the +// pre-existing allocator we're instead not using realloc. +// +// Note also that after SIGSEGV sentry_free() becomes a noop. + +// +// allocates memory with the underlying allocator +// +function sentry_malloc(size: size_t): Pointer; cdecl; external sentry_dll delayed; + +// +// releases memory allocated from the underlying allocator +procedure sentry_free(ptr: Pointer); cdecl; external sentry_dll delayed; + // legacy function. Alias for `sentry_free`. procedure sentry_string_free(str: PAnsiChar); cdecl; - + // -- Protocol Value API -- - + // // Type of a sentry value. // @@ -102,7 +102,7 @@ type SENTRY_VALUE_TYPE_LIST, SENTRY_VALUE_TYPE_OBJECT ); - + // // Represents a sentry protocol value. // @@ -119,20 +119,20 @@ type // automatically happens for some shared values in the event payload like // the module list. // - -type - {sentry_value_u = record - case Integer of - 0: (_bits: UInt64); - 1: (_double: Double); - end; - + +type + {sentry_value_u = record + case Integer of + 0: (_bits: UInt64); + 1: (_double: Double); + end; + sentry_value_t = sentry_value_u;} sentry_value_t = UInt64; - + +// +// Increments the reference count on the value.\ // -// Increments the reference count on the value.\ -// procedure sentry_value_incref( value: sentry_value_t ); cdecl; external sentry_dll delayed; @@ -214,7 +214,7 @@ function sentry_value_new_object: sentry_value_t; cdecl; external sentry_dll de function sentry_value_get_type( value: sentry_value_t ): sentry_value_type_t; cdecl; external sentry_dll delayed; - + // // Sets a key to a value in the map. // @@ -234,8 +234,8 @@ function sentry_value_remove_by_key( value: sentry_value_t; const k: PAnsiChar ): Integer; cdecl; external sentry_dll delayed; - -// + +// // Appends a value to a list. // // This moves the ownership of the value into the list. The caller does not @@ -278,9 +278,12 @@ function sentry_value_get_by_key( const k: PAnsiChar ): sentry_value_t; cdecl; external sentry_dll delayed; - // - // Looks up a value in a map by key. If missing a null value is returned. - // The returned value is owned. + +// + +// Looks up a value in a map by key. If missing a null value is returned. + +// The returned value is owned. // // If the caller no longer needs the value it must be released with // `sentry_value_decref`. @@ -376,20 +379,20 @@ type sentry_level_e = ( sentry_level_t = sentry_level_e; // -// Creates a new empty Event value. -// -// See https://docs.sentry.io/platforms/native/enriching-events/ for how to -// further work with events, and https://develop.sentry.dev/sdk/event-payloads/ -// for a detailed overview of the possible properties of an Event. -// +// Creates a new empty Event value. +// +// See https://docs.sentry.io/platforms/native/enriching-events/ for how to +// further work with events, and https://develop.sentry.dev/sdk/event-payloads/ +// for a detailed overview of the possible properties of an Event. +// function sentry_value_new_event: sentry_value_t; cdecl; external sentry_dll delayed; // // Creates a new Message Event value. -// -// See https://develop.sentry.dev/sdk/event-payloads/message/ -// -// `logger` can be NULL to omit the logger value. +// +// See https://develop.sentry.dev/sdk/event-payloads/message/ +// +// `logger` can be NULL to omit the logger value. // function sentry_value_new_message_event( level: sentry_level_t; @@ -399,8 +402,8 @@ function sentry_value_new_message_event( // // Creates a new Breadcrumb with a specific type and message. -// -// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ +// +// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ // function sentry_value_new_breadcrumb( const _type: PAnsiChar; @@ -408,73 +411,87 @@ function sentry_value_new_breadcrumb( ): sentry_value_t; cdecl; external sentry_dll delayed; // -// Creates a new Exception value. -// -// This is intended for capturing language-level exception, such as from a -// try-catch block. `type` and `value` here refer to the exception class and -// a possible description. -// -// See https://develop.sentry.dev/sdk/event-payloads/exception/ -// -// The returned value needs to be attached to an event via -// `sentry_event_add_exception`. -// -function {SENTRY_EXPERIMENTAL_API} sentry_value_new_exception( - const _type: PAnsiChar; - const value: PAnsiChar -): sentry_value_t; cdecl; external sentry_dll delayed; - -// -// Creates a new Thread value. -// -// See https://develop.sentry.dev/sdk/event-payloads/threads/ -// -// The returned value needs to be attached to an event via -// `sentry_event_add_thread`. -// -// `name` can be NULL. -// -function {SENTRY_EXPERIMENTAL_API} sentry_value_new_thread( - id: UInt64; - const name: PAnsiChar -): sentry_value_t; cdecl; external sentry_dll delayed; - -// -// Creates a new Stack Trace conforming to the Stack Trace Interface. -// -// See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ -// -// The returned object needs to be attached to either an exception -// event, or a thread object. -// -// If `ips` is NULL the current stack trace is captured, otherwise `len` -// stack trace instruction pointers are attached to the event. -// -function {SENTRY_EXPERIMENTAL_API} sentry_value_new_stacktrace( - ips: PPVoid; - len: NativeUInt -): sentry_value_t; cdecl; external sentry_dll delayed; - -// -// Adds an Exception to an Event value. -// -// This takes ownership of the `exception`. -// -procedure {SENTRY_EXPERIMENTAL_API} sentry_event_add_exception( - event: sentry_value_t; - exception: sentry_value_t -); cdecl; external sentry_dll delayed; - -// -// Adds a Thread to an Event value. -// -// This takes ownership of the `thread`. -// -procedure {SENTRY_EXPERIMENTAL_API} sentry_event_add_thread( - event: sentry_value_t; - thread: sentry_value_t -); cdecl; external sentry_dll delayed; - +// Creates a new Exception value. +// +// This is intended for capturing language-level exception, such as from a +// try-catch block. `type` and `value` here refer to the exception class and +// a possible description. +// +// See https://develop.sentry.dev/sdk/event-payloads/exception/ +// +// The returned value needs to be attached to an event via +// `sentry_event_add_exception`. +// +function {SENTRY_EXPERIMENTAL_API} sentry_value_new_exception( + const _type: PAnsiChar; + const value: PAnsiChar +): sentry_value_t; cdecl; external sentry_dll delayed; + +// +// Creates a new Thread value. +// +// See https://develop.sentry.dev/sdk/event-payloads/threads/ +// +// The returned value needs to be attached to an event via +// `sentry_event_add_thread`. +// +// `name` can be NULL. +// +function {SENTRY_EXPERIMENTAL_API} sentry_value_new_thread( + id: UInt64; + const name: PAnsiChar +): sentry_value_t; cdecl; external sentry_dll delayed; + +// +// Creates a new Stack Trace conforming to the Stack Trace Interface. +// +// See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ +// +// The returned object needs to be attached to either an exception +// event, or a thread object. +// +// If `ips` is NULL the current stack trace is captured, otherwise `len` +// stack trace instruction pointers are attached to the event. +// +function {SENTRY_EXPERIMENTAL_API} sentry_value_new_stacktrace( + ips: PPVoid; + len: NativeUInt +): sentry_value_t; cdecl; external sentry_dll delayed; + +// +// Sets the Stack Trace conforming to the Stack Trace Interface in a value. +// +// The value argument must be either an exception or thread object. +// +// If `ips` is NULL the current stack trace is captured, otherwise `len` stack +// trace instruction pointers are attached to the event. +// +procedure {SENTRY_EXPERIMENTAL_API} sentry_value_set_stacktrace( + value: sentry_value_t; + ips: PPVoid; + len: NativeUInt +); cdecl; external sentry_dll delayed; + +// +// Adds an Exception to an Event value. +// +// This takes ownership of the `exception`. +// +procedure {SENTRY_EXPERIMENTAL_API} sentry_event_add_exception( + event: sentry_value_t; + exception: sentry_value_t +); cdecl; external sentry_dll delayed; + +// +// Adds a Thread to an Event value. +// +// This takes ownership of the `thread`. +// +procedure {SENTRY_EXPERIMENTAL_API} sentry_event_add_thread( + event: sentry_value_t; + thread: sentry_value_t +); cdecl; external sentry_dll delayed; + // -- Experimental APIs -- // @@ -493,33 +510,35 @@ function sentry_value_to_msgpack( // Adds a stack trace to an event. // // The stack trace is added as part of a new thread object. -// This function is **deprecated** in favor of using -// `sentry_value_new_stacktrace` in combination with `sentry_value_new_thread` -// and `sentry_event_add_thread`. -// -// If `ips` is NULL the current stack trace is captured, otherwise `len` -// stack trace instruction pointers are attached to the event. +// This function is **deprecated** in favor of using +// `sentry_value_new_stacktrace` in combination with `sentry_value_new_thread` +// and `sentry_event_add_thread`. +// +// If `ips` is NULL the current stack trace is captured, otherwise `len` +// stack trace instruction pointers are attached to the event. // procedure sentry_event_value_add_stacktrace( event: sentry_value_t; ips: Pointer; len: Integer - ); cdecl; external sentry_dll delayed; - - // -// This represents the OS dependent user context in the case of a crash, and can -// be used to manually capture a crash. +); cdecl; external sentry_dll delayed; + + + +// +// This represents the OS dependent user context in the case of a crash, and can +// be used to manually capture a crash. // type sentry_ucontext_s = record -{$IF DEFINED(MSWINDOWS)} - exception_ptrs: EXCEPTION_POINTERS; -{$ELSE} - signum: Integer; - siginfo: siginfo_t; +{$IF DEFINED(MSWINDOWS)} + exception_ptrs: EXCEPTION_POINTERS; +{$ELSE} + signum: Integer; + siginfo: siginfo_t; user_context: ucontext_t; -{$ENDIF} +{$ENDIF} end; sentry_ucontext_t = sentry_ucontext_s; @@ -551,9 +570,10 @@ function sentry_unwind_stack_from_ucontext( max_len: size_t ): size_t; cdecl; external sentry_dll delayed; - - - // + + + +// // A UUID // type @@ -613,12 +633,12 @@ procedure sentry_uuid_as_string( type // -// A Sentry Envelope. -// -// The Envelope is an abstract type which represents a payload being sent to -// sentry. It can contain one or more items, typically an Event. -// See https://develop.sentry.dev/sdk/envelopes/ -// +// A Sentry Envelope. +// +// The Envelope is an abstract type which represents a payload being sent to +// sentry. It can contain one or more items, typically an Event. +// See https://develop.sentry.dev/sdk/envelopes/ +// sentry_envelope_s = record end; sentry_envelope_t = sentry_envelope_s; psentry_envelope_t = Pointer; @@ -626,9 +646,9 @@ type // // Frees an envelope. // -procedure sentry_envelope_free( - envelope: sentry_envelope_t -); cdecl; external sentry_dll delayed; +procedure sentry_envelope_free( + envelope: sentry_envelope_t +); cdecl; external sentry_dll delayed; // // Given an envelope returns the embedded event if there is one. @@ -639,6 +659,15 @@ function sentry_envelope_get_event( const envelope: psentry_envelope_t ): sentry_value_t; cdecl; external sentry_dll delayed; +// +// Given an Envelope, returns the embedded Transaction if there is one. +// +// This returns a borrowed value to the Transaction in the Envelope. +// +function {SENTRY_EXPERIMENTAL_API} sentry_envelope_get_transaction( + const envelope: psentry_envelope_t +): sentry_value_t; cdecl; external sentry_dll delayed; + // // Serializes the envelope // @@ -651,10 +680,10 @@ function sentry_envelope_serialize( // // Serializes the envelope into a file. -// -// `path` is assumed to be in platform-specific filesystem path encoding. -// -// Returns 0 on success. +// +// `path` is assumed to be in platform-specific filesystem path encoding. +// +// Returns 0 on success. // function sentry_envelope_write_to_file( const envelope: psentry_envelope_t; @@ -663,8 +692,8 @@ function sentry_envelope_write_to_file( // // The Sentry Client Options. -// -// See https://docs.sentry.io/error-reporting/configuration/ +// +// See https://docs.sentry.io/error-reporting/configuration/ // type sentry_options_s = record end; @@ -672,130 +701,161 @@ type psentry_options_t = ^sentry_options_t; // -// This represents an interface for user-defined transports. -// -// Transports are responsible for sending envelopes to sentry and are the last -// step in the event pipeline. -// -// Envelopes will be submitted to the transport in a _fire and forget_ fashion, -// and the transport must send those envelopes _in order_. -// -// A transport has the following hooks, all of which -// take the user provided `state` as last parameter. The transport state needs -// to be set with `sentry_transport_set_state` and typically holds handles and -// other information that can be reused across requests. -// -// * `send_func`: This function will take ownership of an envelope, and is -// responsible for freeing it via `sentry_envelope_free`. -// * `startup_func`: This hook will be called by sentry inside of `sentry_init` -// and instructs the transport to initialize itself. Failures will bubble up -// to `sentry_init`. -// * `shutdown_func`: Instructs the transport to flush its queue and shut down. -// This hook receives a millisecond-resolution `timeout` parameter and should -// return `true` when the transport was flushed and shut down successfully. -// In case of `false`, sentry will log an error, but continue with freeing the -// transport. -// * `free_func`: Frees the transports `state`. This hook might be called even -// though `shutdown_func` returned `false` previously. -// -// The transport interface might be extended in the future with hooks to flush -// its internal queue without shutting down, and to dump its internal queue to -// disk in case of a hard crash. -// +// This represents an interface for user-defined transports. +// +// Transports are responsible for sending envelopes to sentry and are the last +// step in the event pipeline. +// +// Envelopes will be submitted to the transport in a _fire and forget_ fashion, +// and the transport must send those envelopes _in order_. +// +// A transport has the following hooks, all of which +// take the user provided `state` as last parameter. The transport state needs +// to be set with `sentry_transport_set_state` and typically holds handles and +// other information that can be reused across requests. +// +// * `send_func`: This function will take ownership of an envelope, and is +// responsible for freeing it via `sentry_envelope_free`. +// * `startup_func`: This hook will be called by sentry inside of `sentry_init` +// and instructs the transport to initialize itself. Failures will bubble up +// to `sentry_init`. +// * `shutdown_func`: Instructs the transport to flush its queue and shut down. +// This hook receives a millisecond-resolution `timeout` parameter and should +// return `true` when the transport was flushed and shut down successfully. +// In case of `false`, sentry will log an error, but continue with freeing the +// transport. +// * `free_func`: Frees the transports `state`. This hook might be called even +// though `shutdown_func` returned `false` previously. +// +// The transport interface might be extended in the future with hooks to flush +// its internal queue without shutting down, and to dump its internal queue to +// disk in case of a hard crash. +// type sentry_transport_s = record end; psentry_transport_s = ^sentry_transport_s; sentry_transport_t = sentry_transport_s; - psentry_transport_t = ^sentry_transport_t; - - _sentry_transport_new_func = procedure(e: psentry_envelope_t; state: Pointer); cdecl; - _sentry_transport_free_func = procedure(state: Pointer); cdecl; - _sentry_transport_startup_func = function(const options: psentry_options_t; state: Pointer): Integer; cdecl; - _sentry_transport_shutdown_func = function(timeout: UInt64; state: Pointer): Integer; cdecl; - -// -// Creates a new transport with an initial `send_func`. -// -function sentry_transport_new( - send_func: _sentry_transport_new_func -): psentry_transport_t; cdecl; external sentry_dll delayed; - -// -// Sets the transport `state`. -// -// If the state is owned by the transport and needs to be freed, use -// `sentry_transport_set_free_func` to set an appropriate hook. -// -procedure sentry_transport_set_state( - transport: psentry_transport_t; - state: Pointer -); cdecl; external sentry_dll delayed; - -// -// Sets the transport hook to free the transport `state`. -// -procedure sentry_transport_set_free_func( - transport: psentry_transport_t; - free_func: _sentry_transport_free_func -); cdecl; external sentry_dll delayed; - -// -// Sets the transport startup hook. -// -// This hook is called from within `sentry_init` and will get a reference to the -// options which can be used to initialize a transports internal state. -// It should return `0` on success. A failure will bubble up to `sentry_init`. -// -procedure sentry_transport_set_startup_func( - transport: psentry_transport_t; - startup_func: _sentry_transport_startup_func -); cdecl; external sentry_dll delayed; - -// -// Sets the transport shutdown hook. -// -// This hook will receive a millisecond-resolution timeout. -// It should return `0` on success in case all the pending envelopes have been -// sent within the timeout, or `1` if the timeout was hit. -// -procedure sentry_transport_set_shutdown_func( - transport: psentry_transport_t; - shutdown_func: _sentry_transport_shutdown_func -); cdecl; external sentry_dll delayed; - -// -// Generic way to free a transport. -// -procedure sentry_transport_free( - transport: psentry_transport_t -); cdecl; external sentry_dll delayed; - -// -// Create a new function transport. -// -// It is a convenience function which works with a borrowed `data`, and will -// automatically free the envelope, so the user provided function does not need -// to do that. -// -// This function is *deprecated* and will be removed in a future version. -// It is here for backwards compatibility. Users should migrate to the -// `sentry_transport_new` API. -// -function sentry_new_function_transport( - func: _sentry_transport_new_func; - data: Pointer -): psentry_transport_t; cdecl; external sentry_dll delayed; - + psentry_transport_t = ^sentry_transport_t; + + _sentry_transport_new_func = procedure(e: psentry_envelope_t; state: Pointer); cdecl; + _sentry_transport_free_func = procedure(state: Pointer); cdecl; + _sentry_transport_startup_func = function(const options: psentry_options_t; state: Pointer): Integer; cdecl; + _sentry_transport_shutdown_func = function(timeout: UInt64; state: Pointer): Integer; cdecl; + _sentry_transport_flush_func = function(timeout: UInt64; state: Pointer): Integer; cdecl; + +// +// Creates a new transport with an initial `send_func`. +// +function sentry_transport_new( + send_func: _sentry_transport_new_func +): psentry_transport_t; cdecl; external sentry_dll delayed; + +// +// Sets the transport `state`. +// +// If the state is owned by the transport and needs to be freed, use +// `sentry_transport_set_free_func` to set an appropriate hook. +// +procedure sentry_transport_set_state( + transport: psentry_transport_t; + state: Pointer +); cdecl; external sentry_dll delayed; + +// +// Sets the transport hook to free the transport `state`. +// +procedure sentry_transport_set_free_func( + transport: psentry_transport_t; + free_func: _sentry_transport_free_func +); cdecl; external sentry_dll delayed; + +// +// Sets the transport startup hook. +// +// This hook is called from within `sentry_init` and will get a reference to the +// options which can be used to initialize a transports internal state. +// It should return `0` on success. A failure will bubble up to `sentry_init`. +// +procedure sentry_transport_set_startup_func( + transport: psentry_transport_t; + startup_func: _sentry_transport_startup_func +); cdecl; external sentry_dll delayed; + +// +// Sets the transport flush hook. +// +// This hook will receive a millisecond-resolution timeout. +// It should return `0` if all the pending envelopes are +// sent within the timeout, or `1` if the timeout is hit. +// +procedure {SENTRY_API} sentry_transport_set_flush_func( + transport: psentry_transport_t; + flush_func: _sentry_transport_flush_func +); cdecl; external sentry_dll delayed; + + + +// +// Sets the transport shutdown hook. +// +// This hook will receive a millisecond-resolution timeout. +// It should return `0` on success in case all the pending envelopes have been +// sent within the timeout, or `1` if the timeout was hit. +// +procedure sentry_transport_set_shutdown_func( + transport: psentry_transport_t; + shutdown_func: _sentry_transport_shutdown_func +); cdecl; external sentry_dll delayed; + +// +// Generic way to free a transport. +// +procedure sentry_transport_free( + transport: psentry_transport_t +); cdecl; external sentry_dll delayed; + +// +// Create a new function transport. +// +// It is a convenience function which works with a borrowed `data`, and will +// automatically free the envelope, so the user provided function does not need +// to do that. +// +// This function is *deprecated* and will be removed in a future version. +// It is here for backwards compatibility. Users should migrate to the +// `sentry_transport_new` API. +// +function sentry_new_function_transport( + func: _sentry_transport_new_func; + data: Pointer +): psentry_transport_t; cdecl; external sentry_dll delayed; + +// +// This represents an interface for user-defined backends. +// +// Backends are responsible to handle crashes. They are maintained at runtime +// via various life-cycle hooks from the sentry-core. +// +// At this point none of those interfaces are exposed in the API including +// creation and destruction. The main use-case of the backend in the API at this +// point is to disable it via `sentry_options_set_backend` at runtime before it +// is initialized. +// +type + sentry_backend_s = record end; + sentry_backend_t = sentry_backend_s; + psentry_backend_t = Pointer; + // -- Options APIs -- // -// The state of user consent. -// -type sentry_user_consent_t = ( - SENTRY_USER_CONSENT_UNKNOWN = -1, - SENTRY_USER_CONSENT_GIVEN = 1, - SENTRY_USER_CONSENT_REVOKED = 0 -); +// The state of user consent. +// +type sentry_user_consent_t = ( + SENTRY_USER_CONSENT_UNKNOWN = -1, + SENTRY_USER_CONSENT_GIVEN = 1, + SENTRY_USER_CONSENT_REVOKED = 0 +); // // Creates a new options struct. @@ -819,20 +879,20 @@ procedure sentry_options_set_transport( ); cdecl; external sentry_dll delayed; // -// Type of the `before_send` callback. -// -// The callback takes ownership of the `event`, and should usually return that -// same event. In case the event should be discarded, the callback needs to -// call `sentry_value_decref` on the provided event, and return a -// `sentry_value_new_null()` instead. -// -// This function may be invoked inside of a signal handler and must be safe for -// that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. -// On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see -// the documentation on SEH (structured exception handling) for more information -// https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling -// -type +// Type of the `before_send` callback. +// +// The callback takes ownership of the `event`, and should usually return that +// same event. In case the event should be discarded, the callback needs to +// call `sentry_value_decref` on the provided event, and return a +// `sentry_value_new_null()` instead. +// +// This function may be invoked inside of a signal handler and must be safe for +// that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. +// On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see +// the documentation on SEH (structured exception handling) for more information +// https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling +// +type sentry_event_function_t = function( event: sentry_value_t; hint: Pointer; @@ -841,15 +901,80 @@ type // // Sets the `before_send` callback. -// -// See the `sentry_event_function_t` typedef above for more information. -// +// +// See the `sentry_event_function_t` typedef above for more information. +// procedure sentry_options_set_before_send( opts: psentry_options_t; func: sentry_event_function_t; data: Pointer ); cdecl; external sentry_dll delayed; +// +// Type of the `on_crash` callback. +// +// The `on_crash` callback replaces the `before_send` callback for crash events. +// The interface is analogous to `before_send` in that the callback takes +// ownership of the `event`, and should usually return that same event. In case +// the event should be discarded, the callback needs to call +// `sentry_value_decref` on the provided event, and return a +// `sentry_value_new_null()` instead. +// +// Only the `inproc` backend currently fills the passed-in event with useful +// data and processes any modifications to the return value. Since both +// `breakpad` and `crashpad` use minidumps to capture the crash state, the +// passed-in event is empty when using these backends, and they ignore any +// changes to the return value. +// +// If you set this callback in the options, it prevents a concurrently enabled +// `before_send` callback from being invoked in the crash case. This allows for +// better differentiation between crashes and other events and gradual migration +// from existing `before_send` implementations: +// +// - if you have a `before_send` implementation and do not define an `on_crash` +// callback your application will receive both normal and crash events as +// before +// - if you have a `before_send` implementation but only want to handle normal +// events with it, then you can define an `on_crash` callback that returns +// the passed-in event and does nothing else +// - if you are not interested in normal events, but only want to act on +// crashes (within the limits mentioned below), then only define an +// `on_crash` callback with the option to filter (on all backends) or enrich +// (only inproc) the crash event +// +// This function may be invoked inside of a signal handler and must be safe for +// that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. +// On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see +// the documentation on SEH (structured exception handling) for more information +// https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling +// +// Platform-specific behavior: +// +// - does not work with crashpad on macOS. +// - for breakpad on Linux the `uctx` parameter is always NULL. +// - on Windows the crashpad backend can capture fast-fail crashes which +// by-pass SEH. Since `on_crash` is called by a local exception-handler, it will +// not be invoked when such a crash happened, even though a minidump will be +// sent. +// +type + sentry_crash_function_t = function( + const uctx: psentry_ucontext_t; + event: sentry_value_t; + closure: Pointer + ): sentry_value_t; cdecl; + +// +// Sets the `on_crash` callback. +// +// See the `sentry_crash_function_t` typedef above for more information. +// +procedure {SENTRY_API} sentry_options_set_on_crash( + opts: psentry_options_t; + func: sentry_crash_function_t; + data: Pointer +); cdecl; external sentry_dll delayed; + // // Sets the DSN. // @@ -866,21 +991,21 @@ function sentry_options_get_dsn( ): PAnsiChar; cdecl; external sentry_dll delayed; // -// Sets the sample rate, which should be a double between `0.0` and `1.0`. -// Sentry will randomly discard any event that is captured using -// `sentry_capture_event` when a sample rate < 1 is set. -// -procedure sentry_options_set_sample_rate( - opts: psentry_options_t; - sample_rate: double -); cdecl; external sentry_dll delayed; - -// -// Gets the sample rate. -// -function sentry_options_get_sample_rate( - const opts: psentry_options_t -): double; cdecl; external sentry_dll delayed; +// Sets the sample rate, which should be a double between `0.0` and `1.0`. +// Sentry will randomly discard any event that is captured using +// `sentry_capture_event` when a sample rate < 1 is set. +// +procedure sentry_options_set_sample_rate( + opts: psentry_options_t; + sample_rate: double +); cdecl; external sentry_dll delayed; + +// +// Gets the sample rate. +// +function sentry_options_get_sample_rate( + const opts: psentry_options_t +): double; cdecl; external sentry_dll delayed; // // Sets the release. @@ -961,20 +1086,20 @@ function sentry_options_get_ca_certs( ): PAnsiChar; cdecl; external sentry_dll delayed; // -// Configures the name of the http transport thread. -// -procedure sentry_options_set_transport_thread_name( - opts: psentry_options_t; - const name: PAnsiChar -); cdecl; external sentry_dll delayed; - -// -// Returns the configured http transport thread name. -// -function sentry_options_get_transport_thread_name( - const opts: psentry_options_t -): PAnsiChar; cdecl; external sentry_dll delayed; - +// Configures the name of the http transport thread. +// +procedure sentry_options_set_transport_thread_name( + opts: psentry_options_t; + const name: PAnsiChar +); cdecl; external sentry_dll delayed; + +// +// Returns the configured http transport thread name. +// +function sentry_options_get_transport_thread_name( + const opts: psentry_options_t +): PAnsiChar; cdecl; external sentry_dll delayed; + // // Enables or disables debug printing mode. // @@ -991,111 +1116,111 @@ function sentry_options_get_debug( ): Integer; cdecl; external sentry_dll delayed; // -// Sets the number of breadcrumbs being tracked and attached to events. -// -// Defaults to 100. -// -procedure sentry_options_set_max_breadcrumbs( - opts: psentry_options_t; - max_breadcrumbs: NativeUInt -); cdecl; external sentry_dll delayed; - -// -// Gets the number of breadcrumbs being tracked and attached to events. -// -function sentry_options_get_max_breadcrumbs( - const opts: psentry_options_t -): NativeUInt; cdecl; external sentry_dll delayed; - -// -// Type of the callback for logger function. -// -type - sentry_logger_function_t = procedure( - level: sentry_level_t; - const message: PAnsiChar; - args: va_list; - userdata: Pointer - ); cdecl; - -// -// Sets the sentry-native logger function. -// -// Used for logging debug events when the `debug` option is set to true. -// -procedure sentry_options_set_logger( - opts: psentry_options_t; - func: sentry_logger_function_t; - userdata: Pointer -); cdecl; external sentry_dll delayed; - -// -// Enables or disables automatic session tracking. -// -// Automatic session tracking is enabled by default and is equivalent to calling -// `sentry_start_session` after startup. -// There can only be one running session, and the current session will always be -// closed implicitly by `sentry_close`, when starting a new session with -// `sentry_start_session`, or manually by calling `sentry_end_session`. -// -procedure sentry_options_set_auto_session_tracking( - opts: psentry_options_t; - val: Integer -); cdecl; external sentry_dll delayed; - -// -// Returns true if automatic session tracking is enabled. -// -function sentry_options_get_auto_session_tracking( - const opts: psentry_options_t +// Sets the number of breadcrumbs being tracked and attached to events. +// +// Defaults to 100. +// +procedure sentry_options_set_max_breadcrumbs( + opts: psentry_options_t; + max_breadcrumbs: NativeUInt +); cdecl; external sentry_dll delayed; + +// +// Gets the number of breadcrumbs being tracked and attached to events. +// +function sentry_options_get_max_breadcrumbs( + const opts: psentry_options_t +): NativeUInt; cdecl; external sentry_dll delayed; + +// +// Type of the callback for logger function. +// +type + sentry_logger_function_t = procedure( + level: sentry_level_t; + const message: PAnsiChar; + args: va_list; + userdata: Pointer + ); cdecl; + +// +// Sets the sentry-native logger function. +// +// Used for logging debug events when the `debug` option is set to true. +// +procedure sentry_options_set_logger( + opts: psentry_options_t; + func: sentry_logger_function_t; + userdata: Pointer +); cdecl; external sentry_dll delayed; + +// +// Enables or disables automatic session tracking. +// +// Automatic session tracking is enabled by default and is equivalent to calling +// `sentry_start_session` after startup. +// There can only be one running session, and the current session will always be +// closed implicitly by `sentry_close`, when starting a new session with +// `sentry_start_session`, or manually by calling `sentry_end_session`. +// +procedure sentry_options_set_auto_session_tracking( + opts: psentry_options_t; + val: Integer +); cdecl; external sentry_dll delayed; + +// +// Returns true if automatic session tracking is enabled. +// +function sentry_options_get_auto_session_tracking( + const opts: psentry_options_t ): Integer; cdecl; external sentry_dll delayed; // -// Enables or disabled user consent requirements for uploads. -// -// This disables uploads until the user has given the consent to the SDK. -// Consent itself is given with `sentry_user_consent_give` and -// `sentry_user_consent_revoke`. -// -procedure sentry_options_set_require_user_consent( - opts: psentry_options_t; - val: Integer); cdecl; external sentry_dll delayed; - -// -// Returns true if user consent is required. -// -function sentry_options_get_require_user_consent( - const opts: psentry_options_t -): Integer; cdecl; external sentry_dll delayed; - +// Enables or disabled user consent requirements for uploads. // -// Enables or disables on-device symbolication of stack traces. -// -// This feature can have a performance impact, and is enabled by default on -// Android. It is usually only needed when it is not possible to provide debug -// information files for system libraries which are needed for serverside -// symbolication. -// -procedure sentry_options_set_symbolize_stacktraces( - opts: psentry_options_t; - val: Integer -); cdecl; external sentry_dll delayed; - -// -// Returns true if on-device symbolication of stack traces is enabled. -// -function sentry_options_get_symbolize_stacktraces( - const opts: psentry_options_t -): Integer; cdecl; external sentry_dll delayed; - +// This disables uploads until the user has given the consent to the SDK. +// Consent itself is given with `sentry_user_consent_give` and +// `sentry_user_consent_revoke`. +// +procedure sentry_options_set_require_user_consent( + opts: psentry_options_t; + val: Integer); cdecl; external sentry_dll delayed; + +// +// Returns true if user consent is required. +// +function sentry_options_get_require_user_consent( + const opts: psentry_options_t +): Integer; cdecl; external sentry_dll delayed; + +// +// Enables or disables on-device symbolication of stack traces. +// +// This feature can have a performance impact, and is enabled by default on +// Android. It is usually only needed when it is not possible to provide debug +// information files for system libraries which are needed for serverside +// symbolication. +// +procedure sentry_options_set_symbolize_stacktraces( + opts: psentry_options_t; + val: Integer +); cdecl; external sentry_dll delayed; + +// +// Returns true if on-device symbolication of stack traces is enabled. +// +function sentry_options_get_symbolize_stacktraces( + const opts: psentry_options_t +): Integer; cdecl; external sentry_dll delayed; + // // Adds a new attachment to be sent along. // -// `path` is assumed to be in platform-specific filesystem path encoding. -// API Users on windows are encouraged to use `sentry_options_add_attachmentw` -// instead. -// +// `path` is assumed to be in platform-specific filesystem path encoding. +// API Users on windows are encouraged to use `sentry_options_add_attachmentw` +// instead. +// procedure sentry_options_add_attachment( opts: psentry_options_t; const path: PAnsiChar @@ -1105,15 +1230,15 @@ procedure sentry_options_add_attachment( // Sets the path to the crashpad handler if the crashpad backend is used. // // The path defaults to the `crashpad_handler`/`crashpad_handler.exe` -// executable, depending on platform, which is expected to be present in the -// same directory as the app executable. -// -// It is recommended that library users set an explicit handler path, depending +// executable, depending on platform, which is expected to be present in the +// same directory as the app executable. +// +// It is recommended that library users set an explicit handler path, depending // on the directory/executable structure of their app. // -// `path` is assumed to be in platform-specific filesystem path encoding. -// API Users on windows are encouraged to use `sentry_options_set_handler_pathw` -// instead. +// `path` is assumed to be in platform-specific filesystem path encoding. +// API Users on windows are encouraged to use `sentry_options_set_handler_pathw` +// instead. // procedure sentry_options_set_handler_path( opts: psentry_options_t; @@ -1122,33 +1247,33 @@ procedure sentry_options_set_handler_path( // // Sets the path to the Sentry Database Directory. -// -// Sentry will use this path to persist user consent, sessions, and other -// artifacts in case of a crash. This will also be used by the crashpad backend -// if it is configured. -// -// The directory is used for "cached" data, which needs to persist across -// application restarts to ensure proper flagging of release-health sessions, -// but might otherwise be safely purged regularly. -// -// It is roughly equivalent to the type of `AppData/Local` on Windows and -// `XDG_CACHE_HOME` on Linux, and equivalent runtime directories on other -// platforms. -// -// It is recommended that users set an explicit absolute path, depending -// on their apps runtime directory. The path will be created if it does not -// exist, and will be resolved to an absolute path inside of `sentry_init`. The -// directory should not be shared with other application data/configuration, as -// sentry-native will enumerate and possibly delete files in that directory. An -// example might be `$XDG_CACHE_HOME/your-app/sentry` -// -// If no explicit path it set, sentry-native will default to `.sentry-native` in -// the current working directory, with no specific platform-specific handling. -// -// `path` is assumed to be in platform-specific filesystem path encoding. -// API Users on windows are encouraged to use -// `sentry_options_set_database_pathw` instead. -// +// +// Sentry will use this path to persist user consent, sessions, and other +// artifacts in case of a crash. This will also be used by the crashpad backend +// if it is configured. +// +// The directory is used for "cached" data, which needs to persist across +// application restarts to ensure proper flagging of release-health sessions, +// but might otherwise be safely purged regularly. +// +// It is roughly equivalent to the type of `AppData/Local` on Windows and +// `XDG_CACHE_HOME` on Linux, and equivalent runtime directories on other +// platforms. +// +// It is recommended that users set an explicit absolute path, depending +// on their apps runtime directory. The path will be created if it does not +// exist, and will be resolved to an absolute path inside of `sentry_init`. The +// directory should not be shared with other application data/configuration, as +// sentry-native will enumerate and possibly delete files in that directory. An +// example might be `$XDG_CACHE_HOME/your-app/sentry` +// +// If no explicit path it set, sentry-native will default to `.sentry-native` in +// the current working directory, with no specific platform-specific handling. +// +// `path` is assumed to be in platform-specific filesystem path encoding. +// API Users on windows are encouraged to use +// `sentry_options_set_database_pathw` instead. +// procedure sentry_options_set_database_path( opts: psentry_options_t; const path: PAnsiChar @@ -1193,6 +1318,37 @@ procedure sentry_options_set_system_crash_reporter_enabled( enabled: Integer ); cdecl; external sentry_dll delayed; +// +// Sets the maximum time (in milliseconds) to wait for the asynchronous tasks to +// end on shutdown, before attempting a forced termination. +// +procedure {SENTRY_API} sentry_options_set_shutdown_timeout( + opts: psentry_options_t; + shutdown_timeout: UInt64 +); cdecl; external sentry_dll delayed; + +// +// Gets the maximum time (in milliseconds) to wait for the asynchronous tasks to +// end on shutdown, before attempting a forced termination. +// +function {SENTRY_API} sentry_options_get_shutdown_timeout( + opts: psentry_options_t +): UInt64; cdecl; external sentry_dll delayed; + +// +// Sets a user-defined backend. +// +// Since creation and destruction of backends is not exposed in the API, this +// can only be used to set the backend to `NULL`, which disables the backend in +// the initialization. +// +procedure {SENTRY_API} sentry_options_set_backend( + opts: psentry_options_t; + backend: psentry_backend_t +); cdecl; external sentry_dll delayed; + + + // -- Global APIs -- // @@ -1201,33 +1357,45 @@ procedure sentry_options_set_system_crash_reporter_enabled( // This takes ownership of the options. After the options have been set they // cannot be modified any more. // Depending on the configured transport and backend, this function might not be -// fully thread-safe. -// Returns 0 on success. +// fully thread-safe. +// Returns 0 on success. // function sentry_init( options: psentry_options_t ): Integer; cdecl; external sentry_dll delayed; +// +// Instructs the transport to flush its send queue. +// +// The `timeout` parameter is in milliseconds. +// +// Returns 0 on success, or a non-zero return value in case the timeout is hit. +// +function {SENTRY_API} sentry_flush( + timeout: UInt64 +): Integer; cdecl; external sentry_dll delayed; + + // // Shuts down the sentry client and forces transports to flush out. // -// Returns 0 on success. -// -function sentry_close: Integer; cdecl; external sentry_dll delayed; - -// -// Shuts down the sentry client and forces transports to flush out. -// -// This is a **deprecated** alias for `sentry_close`. -// -// Returns 0 on success. -// -function sentry_shutdown: Integer; cdecl; external sentry_dll delayed; - -// -// This will lazily load and cache a list of all the loaded libraries. -// -// Returns a new reference to an immutable, frozen list. +// Returns 0 on success. +// +function sentry_close: Integer; cdecl; external sentry_dll delayed; + +// +// Shuts down the sentry client and forces transports to flush out. +// +// This is a **deprecated** alias for `sentry_close`. +// +// Returns 0 on success. +// +function sentry_shutdown: Integer; cdecl; external sentry_dll delayed; + +// +// This will lazily load and cache a list of all the loaded libraries. +// +// Returns a new reference to an immutable, frozen list. // The reference must be released with `sentry_value_decref`. // function sentry_get_modules_list: sentry_value_t; cdecl; external sentry_dll delayed; @@ -1236,43 +1404,43 @@ function sentry_get_modules_list: sentry_value_t; cdecl; external sentry_dll de // Clears the internal module cache. // // For performance reasons, sentry will cache the list of loaded libraries when -// capturing events. This cache can get out-of-date when loading or unloading -// libraries at runtime. It is therefore recommended to call -// `sentry_clear_modulecache` when doing so, to make sure that the next call to +// capturing events. This cache can get out-of-date when loading or unloading +// libraries at runtime. It is therefore recommended to call +// `sentry_clear_modulecache` when doing so, to make sure that the next call to // `sentry_capture_event` will have an up-to-date module list. // procedure sentry_clear_modulecache; cdecl; external sentry_dll delayed; // // Re-initializes the Sentry backend. -// -// This is needed if a third-party library overrides the previously installed -// signal handler. Calling this function can be potentially dangerous and should -// only be done when necessary. -// -// Returns 0 on success. -// +// +// This is needed if a third-party library overrides the previously installed +// signal handler. Calling this function can be potentially dangerous and should +// only be done when necessary. +// +// Returns 0 on success. +// function sentry_reinstall_backend: Integer; cdecl; external sentry_dll delayed; // -// Gives user consent. -// -procedure sentry_user_consent_give; external sentry_dll delayed; - -// -// Revokes user consent. -// -procedure sentry_user_consent_revoke; external sentry_dll delayed; - -// -// Resets the user consent (back to unknown). -// -procedure sentry_user_consent_reset; external sentry_dll delayed; - -// -// Checks the current state of user consent. -// -function sentry_user_consent_get: sentry_user_consent_t; external sentry_dll delayed; +// Gives user consent. +// +procedure sentry_user_consent_give; external sentry_dll delayed; + +// +// Revokes user consent. +// +procedure sentry_user_consent_revoke; external sentry_dll delayed; + +// +// Resets the user consent (back to unknown). +// +procedure sentry_user_consent_reset; external sentry_dll delayed; + +// +// Checks the current state of user consent. +// +function sentry_user_consent_get: sentry_user_consent_t; external sentry_dll delayed; // // Sends a sentry event. @@ -1282,14 +1450,14 @@ function sentry_capture_event( ): sentry_uuid_t; cdecl; external sentry_dll delayed; // -// Captures an exception to be handled by the backend. -// -// This is safe to be called from a crashing thread and may not return. -// -procedure sentry_handle_exception( - const uctx: psentry_ucontext_t -); cdecl; external sentry_dll delayed; - +// Captures an exception to be handled by the backend. +// +// This is safe to be called from a crashing thread and may not return. +// +procedure sentry_handle_exception( + const uctx: psentry_ucontext_t +); cdecl; external sentry_dll delayed; + // // Adds the breadcrumb to be sent in case of an event. // @@ -1357,8 +1525,8 @@ procedure sentry_remove_context( // // Sets the event fingerprint. // -// This accepts a variable number of arguments, and needs to be terminated by a -// trailing `NULL`. +// This accepts a variable number of arguments, and needs to be terminated by a +// trailing `NULL`. // procedure sentry_set_fingerprint( const fingerprint: PAnsiChar @@ -1389,36 +1557,43 @@ procedure sentry_set_level( level: sentry_level_t ); cdecl; external sentry_dll delayed; - // -// Starts a new session. -// -procedure sentry_start_session; cdecl; external sentry_dll delayed; - -// -// Ends a session. -// -procedure sentry_end_session; cdecl; external sentry_dll delayed; - // - // Sets the path to sentry.dll; this must - // be called before any other sentry apis - // - procedure sentry_set_library_path(const path: string); - - implementation - -uses - System.SysUtils; - -procedure sentry_set_library_path(const path: string); -begin - if LoadLibrary(PChar(path)) = 0 then - RaiseLastOSError; -end; - -procedure sentry_string_free(str: PAnsiChar); cdecl; -begin - sentry_free(str); -end; - -end. +// +// Starts a new session. +// +procedure sentry_start_session; cdecl; external sentry_dll delayed; + +// +// Ends a session. +// +procedure sentry_end_session; cdecl; external sentry_dll delayed; + + +// + +// Sets the path to sentry.dll; this must + +// be called before any other sentry apis + +// + +procedure sentry_set_library_path(const path: string); + + +implementation + +uses + System.SysUtils; + +procedure sentry_set_library_path(const path: string); +begin + if LoadLibrary(PChar(path)) = 0 then + RaiseLastOSError; +end; + +procedure sentry_string_free(str: PAnsiChar); cdecl; +begin + sentry_free(str); +end; + +end. diff --git a/common/windows/delphi/ext/sentry/sentry.x64.dll b/common/windows/delphi/ext/sentry/sentry.x64.dll index 9737a5acc0..5587ebaa0c 100644 Binary files a/common/windows/delphi/ext/sentry/sentry.x64.dll and b/common/windows/delphi/ext/sentry/sentry.x64.dll differ diff --git a/common/windows/delphi/ext/sentry/sentry.x64.lib b/common/windows/delphi/ext/sentry/sentry.x64.lib index 1a9f57d57c..31fe092754 100644 Binary files a/common/windows/delphi/ext/sentry/sentry.x64.lib and b/common/windows/delphi/ext/sentry/sentry.x64.lib differ diff --git a/common/windows/delphi/ext/sentry/test/Sentry.Client.Test.SentryClientTestMain.pas b/common/windows/delphi/ext/sentry/test/Sentry.Client.Test.SentryClientTestMain.pas index c9f33f6533..49fe1bc91c 100644 --- a/common/windows/delphi/ext/sentry/test/Sentry.Client.Test.SentryClientTestMain.pas +++ b/common/windows/delphi/ext/sentry/test/Sentry.Client.Test.SentryClientTestMain.pas @@ -7,6 +7,7 @@ procedure main; implementation uses + sentry, Sentry.Client, System.SysUtils; @@ -29,7 +30,7 @@ begin //o.DSN := 'https://7b1ff1dae2c8495b84f90dadcf512b84@sentry.io/4853461'; o.Release := 'keyman-14.0.22-alpha-local'; o.HandlerPath := 'c:\Projects\keyman\app\windows\src\ext\sentry\test\Win32\Release\crashpad_handler.exe'; - o.DatabasePath := '.\sentry-0.4.9-db'; + o.DatabasePath := '.\sentry-'+SENTRY_SDK_VERSION+'-db'; Result := TSentryClient.Create(o); end; diff --git a/common/windows/delphi/general/Keyman.System.KeymanSentryClient.pas b/common/windows/delphi/general/Keyman.System.KeymanSentryClient.pas index 5504f66f9e..8c195f059d 100644 --- a/common/windows/delphi/general/Keyman.System.KeymanSentryClient.pas +++ b/common/windows/delphi/general/Keyman.System.KeymanSentryClient.pas @@ -375,7 +375,7 @@ begin end; o.HandlerPath := ExtractFilePath(path) + 'crashpad_handler.exe'; - o.DatabasePath := TKeymanPaths.ErrorLogPath + 'sentry-0.4.9-db'; + o.DatabasePath := TKeymanPaths.ErrorLogPath + 'sentry-'+SENTRY_SDK_VERSION+'-db'; FClient := SentryClientClass.Create(o, ALogger, f); FClient.OnAfterEvent := ClientAfterEvent; diff --git a/core/build.sh b/core/build.sh index caaeafb929..c1ad0c3302 100755 --- a/core/build.sh +++ b/core/build.sh @@ -93,12 +93,6 @@ if builder_is_dep_build || builder_has_option --no-tests; then builder_remove_dep /developer/src/kmc fi -if builder_is_debug_build; then - CONFIGURATION=debug -else - CONFIGURATION=release -fi - # 'mac' target builds both x86_64 and arm architectures and # generates a 'fat' library from them. builder_describe_internal_dependency \ @@ -106,20 +100,20 @@ builder_describe_internal_dependency \ build:mac build:mac-arm64 builder_describe_outputs \ - configure:x86 /core/build/x86/$CONFIGURATION/build.ninja \ - configure:x64 /core/build/x64/$CONFIGURATION/build.ninja \ - configure:mac /core/build/mac/$CONFIGURATION/ \ - configure:mac-x86_64 /core/build/mac-x86_64/$CONFIGURATION/build.ninja \ - configure:mac-arm64 /core/build/mac-arm64/$CONFIGURATION/build.ninja \ - configure:arch /core/build/arch/$CONFIGURATION/build.ninja \ - configure:wasm /core/build/wasm/$CONFIGURATION/build.ninja \ - build:x86 /core/build/x86/$CONFIGURATION/src/libkmnkbp0.a \ - build:x64 /core/build/x64/$CONFIGURATION/src/libkmnkbp0.a \ - build:mac /core/build/mac/$CONFIGURATION/libkmnkbp0.a \ - build:mac-x86_64 /core/build/mac-x86_64/$CONFIGURATION/src/libkmnkbp0.a \ - build:mac-arm64 /core/build/mac-arm64/$CONFIGURATION/src/libkmnkbp0.a \ - build:arch /core/build/arch/$CONFIGURATION/src/libkmnkbp0.a \ - build:wasm /core/build/wasm/$CONFIGURATION/src/libkmnkbp0.a + configure:x86 /core/build/x86/$BUILDER_CONFIGURATION/build.ninja \ + configure:x64 /core/build/x64/$BUILDER_CONFIGURATION/build.ninja \ + configure:mac /core/build/mac/$BUILDER_CONFIGURATION/ \ + configure:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/build.ninja \ + configure:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/build.ninja \ + configure:arch /core/build/arch/$BUILDER_CONFIGURATION/build.ninja \ + configure:wasm /core/build/wasm/$BUILDER_CONFIGURATION/build.ninja \ + build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ + build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ + build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a \ + build:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ + build:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ + build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ + build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkmnkbp0.a # Iterate through all possible targets; note that targets that cannot be built # on the current platform have already been excluded through the archtargets @@ -129,7 +123,7 @@ targets=(wasm x86 x64 mac-x86_64 mac-arm64 arch) do_action() { local action_function=do_$1 for target in "${targets[@]}"; do - MESON_PATH="$KEYMAN_ROOT/core/build/$target/$CONFIGURATION" + MESON_PATH="$KEYMAN_ROOT/core/build/$target/$BUILDER_CONFIGURATION" $action_function $target done } @@ -145,7 +139,7 @@ do_action configure # After we have built the necessary internal dependencies, then we can go # ahead and build a fat library for external consumption if builder_start_action configure:mac; then - mkdir -p "$KEYMAN_ROOT/core/build/mac/$CONFIGURATION" + mkdir -p "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION" builder_finish_action success configure:mac fi @@ -155,9 +149,9 @@ do_action build if builder_start_action build:mac; then lipo -create \ - "$KEYMAN_ROOT/core/build/mac-x86_64/$CONFIGURATION/src/libkmnkbp0.a" \ - "$KEYMAN_ROOT/core/build/mac-arm64/$CONFIGURATION/src/libkmnkbp0.a" \ - -output "$KEYMAN_ROOT/core/build/mac/$CONFIGURATION/libkmnkbp0.a" + "$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ + "$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ + -output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a" builder_finish_action success build:mac fi @@ -170,7 +164,7 @@ if builder_start_action test:mac; then # assume that build:mac has run so both architectures will be # available target=mac-`uname -m` - MESON_PATH="$KEYMAN_ROOT/core/build/$target/$CONFIGURATION" + MESON_PATH="$KEYMAN_ROOT/core/build/$target/$BUILDER_CONFIGURATION" meson test -C "$MESON_PATH" "${builder_extra_params[@]}" builder_finish_action success test:mac fi diff --git a/core/commands.inc.sh b/core/commands.inc.sh index f3eda70e6d..2e5fb9e7b9 100644 --- a/core/commands.inc.sh +++ b/core/commands.inc.sh @@ -37,11 +37,11 @@ do_configure() { fi if [[ $target =~ ^(x86|x64)$ ]]; then - cmd //C build.bat $target $CONFIGURATION configure $BUILD_BAT_keyman_core_tests "${builder_extra_params[@]}" + cmd //C build.bat $target $BUILDER_CONFIGURATION configure $BUILD_BAT_keyman_core_tests "${builder_extra_params[@]}" else pushd "$THIS_SCRIPT_PATH" > /dev/null # Additional arguments are used by Linux build, e.g. -Dprefix=${INSTALLDIR} - meson setup "$MESON_PATH" $MESON_CROSS_FILE --werror --buildtype $CONFIGURATION $STANDARD_MESON_ARGS "${builder_extra_params[@]}" + meson setup "$MESON_PATH" $MESON_CROSS_FILE --werror --buildtype $BUILDER_CONFIGURATION $STANDARD_MESON_ARGS "${builder_extra_params[@]}" popd > /dev/null fi @@ -56,7 +56,7 @@ do_build() { local target=$1 builder_start_action build:$target || return 0 if [[ $target =~ ^(x86|x64)$ ]]; then - cmd //C build.bat $target $CONFIGURATION build "${builder_extra_params[@]}" + cmd //C build.bat $target $BUILDER_CONFIGURATION build "${builder_extra_params[@]}" elif $MESON_LOW_VERSION; then pushd "$MESON_PATH" > /dev/null ninja @@ -75,7 +75,7 @@ do_test() { local target=$1 builder_start_action test:$target || return 0 if [[ $target =~ ^(x86|x64)$ ]]; then - cmd //C build.bat $target $CONFIGURATION test "${builder_extra_params[@]}" + cmd //C build.bat $target $BUILDER_CONFIGURATION test "${builder_extra_params[@]}" else meson test -C "$MESON_PATH" "${builder_extra_params[@]}" fi diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index 4131a710e9..d06bcc3731 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -724,7 +724,6 @@ COMP_KMXPLUS_LIST::valid(KMX_DWORD _kmn_unused(length)) const { assert(false); return false; } - // TODO-LDML: further validation in the COMP_KMXPLUS_LIST_Helper class return true; } @@ -892,7 +891,7 @@ KMX_DWORD COMP_KMXPLUS_STRS::find(const std::u16string& s) const { if (s.empty()) { return 0; // shortcut } - // TODO-LDML: You're not going to search these linearly, reinterpreting each time??! + // TODO-LDML: suboptimal, but currently only run from the test runner. Could be a binary search since the strings are already in codepoint order. for (KMX_DWORD i = 0; i * UnicodeSets > * spec: ABNT2 key has hex value 0xC1 (even if kbdus.dll doesn't produce that) diff --git a/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml b/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml index d890590e5a..b0c02180a8 100644 --- a/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml +++ b/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml @@ -10,26 +10,27 @@ - - + + + - - - - - + + + + + + - - - - - + + + + - + @@ -48,61 +49,59 @@ - - - - - - - - - - - - - - - - + + - - - + + + + + + + + + + + + + + + - - - - - - - + + + - + + + + + + - - + - + @@ -123,28 +122,28 @@ - - + + - + - - + + - + diff --git a/core/tests/unit/ldml/keyboards/k_001_tiny.xml b/core/tests/unit/ldml/keyboards/k_001_tiny.xml index 998599ede5..65bf21ea51 100644 --- a/core/tests/unit/ldml/keyboards/k_001_tiny.xml +++ b/core/tests/unit/ldml/keyboards/k_001_tiny.xml @@ -21,7 +21,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml b/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml index f6b59e89a9..85d5f7d943 100644 --- a/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml +++ b/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml @@ -20,7 +20,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_003_transform.xml b/core/tests/unit/ldml/keyboards/k_003_transform.xml index 4002fa2c92..747c2c0d3b 100644 --- a/core/tests/unit/ldml/keyboards/k_003_transform.xml +++ b/core/tests/unit/ldml/keyboards/k_003_transform.xml @@ -24,7 +24,7 @@ Note: keys should start with [K_A][K_B][K_C] not [K_Y] when transforms are funct - + diff --git a/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml index a900da9efe..212e41d8fe 100644 --- a/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml +++ b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml @@ -21,7 +21,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_005_modbittest.xml b/core/tests/unit/ldml/keyboards/k_005_modbittest.xml index 05ee75ed67..8d1a934016 100644 --- a/core/tests/unit/ldml/keyboards/k_005_modbittest.xml +++ b/core/tests/unit/ldml/keyboards/k_005_modbittest.xml @@ -16,7 +16,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_010_mt.xml b/core/tests/unit/ldml/keyboards/k_010_mt.xml index ba3e6ab5db..c0734ac180 100644 --- a/core/tests/unit/ldml/keyboards/k_010_mt.xml +++ b/core/tests/unit/ldml/keyboards/k_010_mt.xml @@ -11,7 +11,8 @@ Gets part of the way, Note this is the 47-key version. --> - + + @@ -19,16 +20,15 @@ Gets part of the way, - - + + - + + - - @@ -41,8 +41,8 @@ Gets part of the way, - - + + @@ -53,13 +53,13 @@ Gets part of the way, + + + - - - - + + @@ -84,7 +84,7 @@ Gets part of the way, - + @@ -92,4 +92,5 @@ Gets part of the way, + diff --git a/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml b/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml index 2b89cef958..f572bac9ec 100644 --- a/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml +++ b/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml @@ -10,7 +10,6 @@ Gets part of the way, Exact copy of mt.xml from CLDR, but with: - an updated DTD path - test case - - changed 'altR-shift' to 'altR shift' --> @@ -21,13 +20,14 @@ Exact copy of mt.xml from CLDR, but with: - - + + + @@ -41,8 +41,8 @@ Exact copy of mt.xml from CLDR, but with: - - + + @@ -55,7 +55,8 @@ Exact copy of mt.xml from CLDR, but with: - + + @@ -88,4 +89,5 @@ Exact copy of mt.xml from CLDR, but with: + diff --git a/core/tests/unit/ldml/keyboards/k_100_keytest.xml b/core/tests/unit/ldml/keyboards/k_100_keytest.xml index d185035c94..8076620494 100644 --- a/core/tests/unit/ldml/keyboards/k_100_keytest.xml +++ b/core/tests/unit/ldml/keyboards/k_100_keytest.xml @@ -18,7 +18,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_101_keytest.xml b/core/tests/unit/ldml/keyboards/k_101_keytest.xml index 16e4c76f91..f3ef3fbdce 100644 --- a/core/tests/unit/ldml/keyboards/k_101_keytest.xml +++ b/core/tests/unit/ldml/keyboards/k_101_keytest.xml @@ -21,7 +21,7 @@ - + diff --git a/core/tests/unit/ldml/keyboards/k_102_keytest.xml b/core/tests/unit/ldml/keyboards/k_102_keytest.xml index 45ab4b71c0..f541fbfd7b 100644 --- a/core/tests/unit/ldml/keyboards/k_102_keytest.xml +++ b/core/tests/unit/ldml/keyboards/k_102_keytest.xml @@ -13,7 +13,7 @@ - + diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 37e10ee48e..219a143632 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -528,7 +528,7 @@ int LdmlJsonTestSourceFactory::load(const km::kbp::path &compiled, const km::kbp // TODO-LDML: store these elsewhere? std::cout << "JSON: reading " << info_name << " test of " << info_keyboard << " by " << info_author << std::endl; - // TODO-LDML: repertoire test + // TODO-LDML: repertoire test #8435 auto all_tests = data["/keyboardTest/tests"_json_pointer]; assert_or_return((!all_tests.empty()) && (all_tests.size() > 0)); diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp index f4bf716c34..7f69112b8e 100644 --- a/core/tests/unit/ldml/ldml_test_source.hpp +++ b/core/tests/unit/ldml/ldml_test_source.hpp @@ -33,7 +33,7 @@ enum ldml_action_type { * expected text */ LDML_ACTION_CHECK_EXPECTED, - // TODO-LDML: gestures, etc? + // TODO-LDML: gestures, etc? Depends on touch. }; struct ldml_action { diff --git a/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas b/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas index c99d7f84c2..a3e07f03b2 100644 --- a/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas +++ b/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas @@ -61,7 +61,7 @@ begin if m.Success then begin msgFilename := m.Groups[1].Value; - msgLine := StrToInt(m.Groups[2].Value); + msgLine := StrToIntDef(m.Groups[2].Value, 0); msgType := m.Groups[3].Value; msgCode := StrToInt('$'+m.Groups[4].Value); msgText := m.Groups[5].Value; diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index 0847b93cc9..1c685ff113 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -55,7 +55,7 @@ - + @@ -211,7 +211,7 @@ - + diff --git a/developer/src/kmc-keyboard/src/compiler/keys.ts b/developer/src/kmc-keyboard/src/compiler/keys.ts index 48da90a04f..5eeaadcbad 100644 --- a/developer/src/kmc-keyboard/src/compiler/keys.ts +++ b/developer/src/kmc-keyboard/src/compiler/keys.ts @@ -15,6 +15,14 @@ export class KeysCompiler extends SectionCompiler { return constants.section.keys; } + /** + * + * @returns just the non-touch layers. + */ + public hardwareLayers() { + return this.keyboard.layers?.filter(({form}) => form !== 'touch'); + } + public validate() { let valid = true; @@ -37,21 +45,22 @@ export class KeysCompiler extends SectionCompiler { } } + // the layr compiler does more extensive validation of the layer attributes. + // Kmap validation - const theLayers = this.keyboard.layers?.[0]; // TODO-LDML: handle >1 layers. #8160 + const hardwareLayers = this.hardwareLayers(); - if(!theLayers?.layer?.length) { - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_MustBeAtLeastOneLayerElement()); - } - - if(theLayers?.form == 'hardware') { - for(let layer of theLayers?.layer) { - valid = this.validateHardwareLayerForKmap(theLayers?.hardware, layer) && valid; // note: always validate even if previously invalid results found + if (hardwareLayers.length >= 1) { + // validate all errors + for (let layers of hardwareLayers) { + for(let layer of layers.layer) { + valid = this.validateHardwareLayerForKmap(layers.form, layer) && valid; // note: always validate even if previously invalid results found + } } + } else { + // TODO-LDML: Touch? } - // TODO-LDML: some additional validation needed here? return valid; } @@ -71,15 +80,17 @@ export class KeysCompiler extends SectionCompiler { // Finally, kmap // Use LayerMap + keys to generate compiled keys for hardware - const theLayers = this.keyboard.layers?.[0]; // TODO-LDML: handle >1 layers. #8160 - - if(theLayers?.form == 'hardware') { + const hardwareLayers = this.hardwareLayers(); + if (hardwareLayers.length > 1) { + // validation should have already caught this + throw Error(`Internal error: Expected 0 or 1 hardware layer, not ${hardwareLayers.length}`); + } else if (hardwareLayers.length === 1) { + const theLayers = hardwareLayers[0]; + const { form } = theLayers; for(let layer of theLayers.layer) { - this.compileHardwareLayerToKmap(sections, layer, sect, theLayers.hardware); + this.compileHardwareLayerToKmap(sections, layer, sect, form); } - return sect; - } - // TODO-LDML: generate vkey mapping for touch-only keys + } // else: TODO-LDML do nothing if only touch layers return sect; } @@ -147,7 +158,7 @@ export class KeysCompiler extends SectionCompiler { /** * TODO-LDML: from old 'keys' * Validate for purpose of kmap - * @param hardware + * @param hardware the 'form' parameter * @param layer * @returns */ @@ -162,9 +173,7 @@ export class KeysCompiler extends SectionCompiler { const keymap = Constants.HardwareToKeymap.get(hardware); if (!keymap) { - this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({ hardware })); - valid = false; - return valid; // can't do anything else here + throw Error(`TODO-LDML: ${hardware} not supported, see #8161`); } const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]); @@ -202,7 +211,6 @@ export class KeysCompiler extends SectionCompiler { return valid; } - private compileHardwareLayerToKmap( sections: GlobalSections, layer: LDMLKeyboard.LKLayer, @@ -234,5 +242,4 @@ export class KeysCompiler extends SectionCompiler { } return sect; } - } diff --git a/developer/src/kmc-keyboard/src/compiler/layr.ts b/developer/src/kmc-keyboard/src/compiler/layr.ts index e20855ed6e..f506b3e237 100644 --- a/developer/src/kmc-keyboard/src/compiler/layr.ts +++ b/developer/src/kmc-keyboard/src/compiler/layr.ts @@ -19,47 +19,41 @@ export class LayrCompiler extends SectionCompiler { public validate() { let valid = true; - if (!this.keyboard.layers?.[0]?.layer?.length) { - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_MustBeAtLeastOneLayerElement()); - } + let totalLayerCount = 0; let hardwareLayers = 0; - this.keyboard.layers.forEach((layers) => { - const { hardware, form } = layers; - // TODO-LDML: in the future >1 hardware layer may be allowed, check for duplicates + // let touchLayers = 0; + this.keyboard.layers?.forEach((layers) => { + const { form } = layers; if (form === 'touch') { - if (hardware) { - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_NoHardwareOnTouch({hardware})); - } - } else if (form === 'hardware') { - hardwareLayers++; - if (!hardware) { - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_MissingHardware()); - } else if (!constants.layr_list_hardware_map.get(hardware)) { - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({hardware})); - } else if (hardwareLayers > 1) { // TODO-LDML: revisit if spec changes - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_MustHaveAtMostOneLayersElementPerForm({ form })); - } + // touchLayers++; + // multiple touch layers are OK + totalLayerCount += layers.layer?.length; + // TODO-LDML: check that widths are distinct } else { - /* c8 ignore next 7 */ - // Should not be reached due to XML validation. - valid = false; - this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({ - errorText: `INTERNAL ERROR: Invalid XML: Invalid form="${form}" on layers element` - })); + // hardware + hardwareLayers++; + if (hardwareLayers > 1) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_ExcessHardware({form})); + } else if (!constants.layr_list_hardware_map.get(form)) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({form})); + } } layers.layer.forEach((layer) => { const { modifier, id } = layer; + totalLayerCount++; if (!validModifier(modifier)) { this.callbacks.reportMessage(CompilerMessages.Error_InvalidModifier({ modifier, layer: id })); valid = false; } }); }); + if (totalLayerCount === 0) { // TODO-LDML: does not validate touch layers yet + // no layers seen anywhere + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MustBeAtLeastOneLayerElement()); + } return valid; } @@ -67,8 +61,8 @@ export class LayrCompiler extends SectionCompiler { const sect = new Layr(); sect.lists = this.keyboard.layers.map((layers) => { - const hardware = constants.layr_list_hardware_map.get(layers.hardware || 'touch'); - // Don't need to check 'form' because it is checked in validate + const hardware = constants.layr_list_hardware_map.get(layers.form); + // Already validated in validate const list: LayrList = { hardware, minDeviceWidth: layers.minDeviceWidth || 0, diff --git a/developer/src/kmc-keyboard/src/compiler/messages.ts b/developer/src/kmc-keyboard/src/compiler/messages.ts index ceacd4be25..202a5bfa42 100644 --- a/developer/src/kmc-keyboard/src/compiler/messages.ts +++ b/developer/src/kmc-keyboard/src/compiler/messages.ts @@ -71,29 +71,24 @@ export class CompilerMessages { m(this.ERROR_KeyMissingToGapOrSwitch, `key id='${o.keyId}' must have either to=, gap=, or switch=.`); static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011; - static Error_MustHaveAtMostOneLayersElementPerForm = (o:{form: string}) => m(this.ERROR_MustHaveAtMostOneLayersElementPerForm, - `Must have at most one layers element with form=${o.form}`); - static ERROR_MustHaveAtMostOneLayersElementPerForm = SevError | 0x0012; + static Error_ExcessHardware = (o:{form: string}) => m(this.ERROR_ExcessHardware, + `layers form=${o.form}: Can only have one non-'touch' element`); + static ERROR_ExcessHardware = SevError | 0x0012; - static Error_NoHardwareOnTouch = (o:{hardware: string}) => m(this.ERROR_NoHardwareOnTouch, - `Not allowed: form=touch with hardware=${o.hardware}`); - static ERROR_NoHardwareOnTouch = SevError | 0x0013; - - static Error_MissingHardware = () => m(this.ERROR_MissingHardware, - `layers form=hardware missing hardware= attribute`); - static ERROR_MissingHardware = SevError | 0x0014; - - static Error_InvalidHardware = (o:{hardware: string}) => m(this.ERROR_InvalidHardware, - `layers has invalid value hardware=${o.hardware}`); - static ERROR_InvalidHardware = SevError | 0x0015; + static Error_InvalidHardware = (o:{form: string}) => m(this.ERROR_InvalidHardware, + `layers has invalid value form=${o.form}`); + /** + * Note: may not hit this due to XML validation. + */ + static ERROR_InvalidHardware = SevError | 0x0013; static Error_InvalidModifier = (o:{layer: string, modifier: string}) => m(this.ERROR_InvalidModifier, `layer has invalid modifier='${o.modifier}' on layer id=${o.layer}`); - static ERROR_InvalidModifier = SevError | 0x0016; + static ERROR_InvalidModifier = SevError | 0x0014; static Error_MissingFlicks = (o:{flicks: string, id: string}) => m(this.ERROR_MissingFlicks, `key id=${o.id} refers to missing flicks=${o.flicks}`); - static ERROR_MissingFlicks = SevError | 0x0017; + static ERROR_MissingFlicks = SevError | 0x0015; static severityName(code: number): string { let severity = code & CompilerErrorSeverity.Severity_Mask; diff --git a/developer/src/kmc-keyboard/test/fixtures/basic.xml b/developer/src/kmc-keyboard/test/fixtures/basic.xml index 32eeebaef1..0703e30c32 100644 --- a/developer/src/kmc-keyboard/test/fixtures/basic.xml +++ b/developer/src/kmc-keyboard/test/fixtures/basic.xml @@ -30,7 +30,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml index c2cbd96c24..d0a968927b 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml @@ -24,7 +24,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml index dcd63b9cd6..fee8847897 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml index 6aae1db0b7..4cba4320e4 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml @@ -13,7 +13,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml index 19bd0a8935..fc282c7a6c 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml @@ -13,7 +13,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml index 6eeeb0bec3..84afa0dddb 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml index de7f29acc5..8182e8c3f0 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml index ea2eaedb44..77f2ff39d1 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml @@ -8,7 +8,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml index 82db56ee96..6c2f01a085 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml @@ -24,7 +24,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml index 466d06e779..f868fb3db3 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml @@ -11,7 +11,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml index b58fed589c..98e2131b5d 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml index 5faa918aa9..4bce8aa31d 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml @@ -14,7 +14,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml index b3d01f4aa8..65a86838ee 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml index 24a60cc94e..61554e8dd9 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml @@ -24,7 +24,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml index c29bfa1fd9..dc498b31f6 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml @@ -10,7 +10,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml deleted file mode 100644 index ec574c7c56..0000000000 --- a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml index bbd4b58585..8d1c63e801 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml @@ -11,7 +11,7 @@ - + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer2.xml similarity index 75% rename from developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml rename to developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer2.xml index dc233a14d0..dd08e40e58 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer2.xml @@ -3,8 +3,9 @@ - + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml index 39644279da..dadef48404 100644 --- a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml @@ -11,15 +11,14 @@ - + - - + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml deleted file mode 100644 index 176d9b13f2..0000000000 --- a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-keyboard/test/helpers/index.ts b/developer/src/kmc-keyboard/test/helpers/index.ts index 0c476313a8..b5d86a93b3 100644 --- a/developer/src/kmc-keyboard/test/helpers/index.ts +++ b/developer/src/kmc-keyboard/test/helpers/index.ts @@ -91,9 +91,10 @@ export function loadSectionFixture(compilerClass: typeof SectionCompiler, filena const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); const source = reader.load(data); assert.isNotNull(source); - assert.doesNotThrow(() => { - reader.validate(source, callbacks.loadLdmlKeyboardSchema()); - }); + + if (!reader.validate(source, callbacks.loadLdmlKeyboardSchema())) { + return null; // mimic kmc behavior - bail if validate fails + } const compiler = new compilerClass(source, callbacks); diff --git a/developer/src/kmc-keyboard/test/test-keys.ts b/developer/src/kmc-keyboard/test/test-keys.ts index b584d7eb4c..c315cd31af 100644 --- a/developer/src/kmc-keyboard/test/test-keys.ts +++ b/developer/src/kmc-keyboard/test/test-keys.ts @@ -10,92 +10,97 @@ const K = Constants.USVirtualKeyCodes; describe('keys', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while - - it('should compile minimal keys data', function () { - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Keys; - assert.ok(keys); - assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(keys.keys.length, 1); - assert.equal(keys.flicks.length, 1); // there's always a 'null' flick - assert.equal(keys.keys[0].to.value, '🪦'); - assert.equal(keys.keys[0].id.value, 'grave'); - }); - - it('should compile maximal keys data', function () { - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/maximal.xml', compilerTestCallbacks) as Keys; - assert.ok(keys); - assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(keys.keys.length, 4); - - const [q] = keys.keys.filter(({ id }) => id.value === 'q'); - assert.ok(q); - assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); - assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) - assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem - assert.equal(q.longPress.toString(), 'á é í'); - assert.equal(q.longPressDefault.value, 'é'); - assert.equal(q.multiTap.toString(), 'ä ë ï'); - - const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); - assert.ok(flick0); - assert.equal(flick0.flicks.length, 2); - - const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); - assert.ok(flick0_nw_se); - assert.equal(flick0_nw_se.to?.value, 'ç'); - - const [flick0_ne_sw] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('ne sw'.split(' '))); - assert.ok(flick0_ne_sw); - assert.equal(flick0_ne_sw.to?.value, 'ê'); - }); - - it('should compile escaped keys data', function () { - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/escaped.xml', compilerTestCallbacks) as Keys; - assert.ok(keys); - assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(keys.keys.length, 4); - - const [q] = keys.keys.filter(({ id }) => id.value === 'q'); - assert.ok(q); - assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); - assert.equal(q.width, 32); // ceil(3.1 * 10) - assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem - assert.equal(q.longPress.toString(), 'á é í'); - assert.equal(q.longPressDefault.value, 'é'); - assert.equal(q.multiTap.toString(), 'ä ë ï'); - - const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); - assert.ok(flick0); - assert.equal(flick0.flicks.length, 2); - - const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); - assert.ok(flick0_nw_se); - assert.equal(flick0_nw_se.to?.value, 'ç'); - - const [flick0_ne_sw] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('ne sw'.split(' '))); - assert.ok(flick0_ne_sw); - assert.equal(flick0_ne_sw.to?.value, 'ế'); - }); - - - it('should accept layouts with gap/switch keys', function () { - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks) as Keys; - assert.ok(keys); - assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(keys.keys.length, 4); - - const [Qgap] = keys.keys.filter(({ id }) => id.value === 'Q'); - assert.ok(Qgap); - assert.isTrue(!!(Qgap.flags & constants.keys_key_flags_gap), 'Q’s gap='); - - const [Wshift] = keys.keys.filter(({ id }) => id.value === 'W'); - assert.isNotNull(Wshift); - assert.isFalse(!!(Wshift.flags & constants.keys_key_flags_gap)); - assert.equal(Wshift.switch.value, 'shift'); - - }); - testCompilationCases(KeysCompiler, [ + { + // should compile minimal keys data + subpath: 'sections/keys/minimal.xml', + callback(sect) { + const keys = sect; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 1); + assert.equal(keys.flicks.length, 1); // there's always a 'null' flick + assert.equal(keys.keys[0].to.value, String.fromCodePoint(0x1FAA6)); + assert.equal(keys.keys[0].id.value, 'grave'); + }, + }, + { + subpath: 'sections/keys/maximal.xml', + callback(sect) { + const keys = sect; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [q] = keys.keys.filter(({ id }) => id.value === 'q'); + assert.ok(q); + assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) + assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem + assert.equal(q.longPress.toString(), 'á é í'); + assert.equal(q.longPressDefault.value, 'é'); + assert.equal(q.multiTap.toString(), 'ä ë ï'); + + const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); + assert.ok(flick0); + assert.equal(flick0.flicks.length, 2); + + const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); + assert.ok(flick0_nw_se); + assert.equal(flick0_nw_se.to?.value, 'ç'); + + const [flick0_ne_sw] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('ne sw'.split(' '))); + assert.ok(flick0_ne_sw); + assert.equal(flick0_ne_sw.to?.value, 'ê'); + }, + }, + { + subpath: 'sections/keys/escaped.xml', + callback(sect) { + const keys = sect; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [q] = keys.keys.filter(({ id }) => id.value === 'q'); + assert.ok(q); + assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.equal(q.width, 32); // ceil(3.1 * 10) + assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem + assert.equal(q.longPress.toString(), 'á é í'); + assert.equal(q.longPressDefault.value, 'é'); + assert.equal(q.multiTap.toString(), 'ä ë ï'); + + const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); + assert.ok(flick0); + assert.equal(flick0.flicks.length, 2); + + const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); + assert.ok(flick0_nw_se); + assert.equal(flick0_nw_se.to?.value, 'ç'); + + const [flick0_ne_sw] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('ne sw'.split(' '))); + assert.ok(flick0_ne_sw); + assert.equal(flick0_ne_sw.to?.value, 'ế'); + }, + }, + { + subpath: 'sections/keys/gap-switch.xml', + callback(sect) { + const keys = sect; + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [Qgap] = keys.keys.filter(({ id }) => id.value === 'Q'); + assert.ok(Qgap); + assert.isTrue(!!(Qgap.flags & constants.keys_key_flags_gap), 'Q’s gap='); + + const [Wshift] = keys.keys.filter(({ id }) => id.value === 'W'); + assert.isNotNull(Wshift); + assert.isFalse(!!(Wshift.flags & constants.keys_key_flags_gap)); + assert.equal(Wshift.switch.value, 'shift'); + }, + }, { subpath: 'sections/keys/escaped2.xml', callback: (keys, subpath, callbacks) => { @@ -212,14 +217,6 @@ describe('keys.kmap', function () { }, ]); - it('should reject structurally invalid layers', function() { - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-missing-layer.xml', compilerTestCallbacks) as Keys; - assert.isNull(keys); - assert.equal(compilerTestCallbacks.messages.length, 1); - - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_MustBeAtLeastOneLayerElement()); - }); - it('should reject layouts with too many hardware rows', function() { let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); diff --git a/developer/src/kmc-keyboard/test/test-layr.ts b/developer/src/kmc-keyboard/test/test-layr.ts index d30d71e2a7..e38a1b728d 100644 --- a/developer/src/kmc-keyboard/test/test-layr.ts +++ b/developer/src/kmc-keyboard/test/test-layr.ts @@ -43,66 +43,57 @@ describe('layr', function () { assert.equal(row0.keys[0]?.value, 'grave'); }); - // reuse keys maximal - it('should compile maximal keys data', function () { - let layr = loadSectionFixture(LayrCompiler, 'sections/keys/maximal.xml', compilerTestCallbacks) as Layr; - assert.ok(layr); - assert.equal(compilerTestCallbacks.messages.length, 0); - - assert.equal(layr.lists?.length, 2); - - const listHardware = layr.lists.find(v => v.hardware === constants.layr_list_hardware_abnt2); - assert.ok(listHardware); - assert.equal(listHardware.minDeviceWidth, 0); - assert.equal(listHardware.layers.length, 2); - const hardware0 = listHardware.layers[0]; - assert.ok(hardware0); - assert.equal(hardware0.id.value, 'base'); - assert.equal(hardware0.mod, constants.keys_mod_none); - const hardware0row0 = hardware0.rows[0]; - assert.ok(hardware0row0); - assert.equal(hardware0row0.keys.length, 2); - allKeysOk(hardware0row0,'Q W', 'hardware0row0'); - const hardware1 = listHardware.layers[1]; - assert.ok(hardware1); - assert.equal(hardware1.rows.length, 1); - assert.equal(hardware1.id.value, 'shift'); - assert.equal(hardware1.mod, constants.keys_mod_shift); - const hardware1row0 = hardware1.rows[0]; - assert.ok(hardware1row0); - assert.equal(hardware1row0.keys.length, 2); - allKeysOk(hardware1row0,'q w', 'hardware1row0'); - - const listTouch = layr.lists.find(v => v.hardware === constants.layr_list_hardware_touch); - assert.ok(listTouch); - assert.equal(listTouch.minDeviceWidth, 300); - assert.equal(listTouch.layers.length, 1); - const touch0 = listTouch.layers[0]; - assert.ok(touch0); - assert.equal(touch0.rows.length, 1); - assert.equal(touch0.id.value, 'base'); - assert.equal(touch0.mod, constants.keys_mod_none); - const touch0row0 = touch0.rows[0]; - assert.ok(touch0row0); - assert.equal(touch0row0.keys.length, 4); - allKeysOk(touch0row0,'Q q W w', 'touch0row0'); - }); testCompilationCases(LayrCompiler, [ { - subpath: 'sections/layr/invalid-invalid-hardware.xml', - errors: [CompilerMessages.Error_InvalidHardware({hardware: 'stenography'})], + subpath: 'sections/keys/maximal.xml', + callback(sect) { + const layr = sect; + assert.equal(layr.lists?.length, 2); + + const listHardware = layr.lists.find(v => v.hardware === constants.layr_list_hardware_iso); + assert.ok(listHardware); + assert.equal(listHardware.minDeviceWidth, 0); + assert.equal(listHardware.layers.length, 2); + const hardware0 = listHardware.layers[0]; + assert.ok(hardware0); + assert.equal(hardware0.id.value, 'base'); + assert.equal(hardware0.mod, constants.keys_mod_none); + const hardware0row0 = hardware0.rows[0]; + assert.ok(hardware0row0); + assert.equal(hardware0row0.keys.length, 2); + allKeysOk(hardware0row0,'Q W', 'hardware0row0'); + const hardware1 = listHardware.layers[1]; + assert.ok(hardware1); + assert.equal(hardware1.rows.length, 1); + assert.equal(hardware1.id.value, 'shift'); + assert.equal(hardware1.mod, constants.keys_mod_shift); + const hardware1row0 = hardware1.rows[0]; + assert.ok(hardware1row0); + assert.equal(hardware1row0.keys.length, 2); + allKeysOk(hardware1row0,'q w', 'hardware1row0'); + + const listTouch = layr.lists.find(v => v.hardware === constants.layr_list_hardware_touch); + assert.ok(listTouch); + assert.equal(listTouch.minDeviceWidth, 300); + assert.equal(listTouch.layers.length, 1); + const touch0 = listTouch.layers[0]; + assert.ok(touch0); + assert.equal(touch0.rows.length, 1); + assert.equal(touch0.id.value, 'base'); + assert.equal(touch0.mod, constants.keys_mod_none); + const touch0row0 = touch0.rows[0]; + assert.ok(touch0row0); + assert.equal(touch0row0.keys.length, 4); + allKeysOk(touch0row0,'Q q W w', 'touch0row0'); + }, }, { subpath: 'sections/layr/invalid-missing-hardware.xml', - errors: [CompilerMessages.Error_MissingHardware()], + errors: [], }, { subpath: 'sections/layr/invalid-multi-hardware.xml', - errors: [CompilerMessages.Error_MustHaveAtMostOneLayersElementPerForm({ form: 'hardware' })], - }, - { - subpath: 'sections/layr/invalid-touch-hardware.xml', - errors: [CompilerMessages.Error_NoHardwareOnTouch({ hardware: 'iso' })], + errors: [CompilerMessages.Error_ExcessHardware({ form: 'iso' })], }, { subpath: 'sections/layr/invalid-invalid-form.xml', @@ -110,11 +101,19 @@ describe('layr', function () { instancePath: '/keyboard/layers/0/form', keyword: 'enum', message: 'must be equal to one of the allowed values', - params: `allowedValues="hardware,touch"`}),], + params: `allowedValues="touch,us,iso,jis,abnt2"`}),], }, { + // missing layer element subpath: 'sections/layr/invalid-missing-layer.xml', errors: [CompilerMessages.Error_MustBeAtLeastOneLayerElement()], }, + { + // missing layers element completely + subpath: 'sections/layr/invalid-missing-layer.xml', + errors: [ + CompilerMessages.Error_MustBeAtLeastOneLayerElement(), + ], + } ]); }); diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index 1fe6aa45fd..67ba3c38ba 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -12,10 +12,10 @@ "scripts": { "build": "tsc -b", "bundle": "npm run bundle-kmc && npm run bundle-kmlmc && npm run bundle-kmlmi && npm run bundle-kmlmp", - "bundle-kmc": "esbuild build/src/kmc.js --bundle --platform=node > build/cjs-src/kmc.cjs", - "bundle-kmlmc": "esbuild build/src/kmlmc.js --bundle --platform=node > build/cjs-src/kmlmc.cjs", - "bundle-kmlmi": "esbuild build/src/kmlmi.js --bundle --platform=node > build/cjs-src/kmlmi.cjs", - "bundle-kmlmp": "esbuild build/src/kmlmp.js --bundle --platform=node > build/cjs-src/kmlmp.cjs", + "bundle-kmc": "esbuild build/src/kmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmc.cjs", + "bundle-kmlmc": "esbuild build/src/kmlmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmc.cjs", + "bundle-kmlmi": "esbuild build/src/kmlmi.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmi.cjs", + "bundle-kmlmp": "esbuild build/src/kmlmp.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmp.cjs", "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", "prepublishOnly": "npm run build" }, diff --git a/developer/src/tike/Makefile b/developer/src/tike/Makefile index f9a4c61d36..ce20a3673c 100644 --- a/developer/src/tike/Makefile +++ b/developer/src/tike/Makefile @@ -68,6 +68,10 @@ clean: def-clean signcode: $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\tike.exe $(SIGNCODE) /d "Keyman Core" $(DEVELOPER_PROGRAM)\kmnkbp0-0.dll +# Sign the Sentry executables and libraries here + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\sentry.dll + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\sentry.x64.dll + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\crashpad_handler.exe wrap-symbols: $(SYMSTORE) $(DEVELOPER_PROGRAM)\tike.exe /t keyman-developer diff --git a/docs/settings/linux/tasks.json b/docs/settings/linux/tasks.json index e7f26215a6..77df789fe3 100644 --- a/docs/settings/linux/tasks.json +++ b/docs/settings/linux/tasks.json @@ -1,18 +1,41 @@ { "version": "2.0.0", "tasks": [ + { + "type": "shell", + "label": "core: clean", + "command": "${workspaceFolder}/core/build.sh", + "args": [ + "--debug", + "clean" + ], + "group": "build", + "detail": "clean core" + }, + { + "type": "shell", + "label": "core: configure", + "command": "${workspaceFolder}/core/build.sh", + "args": [ + "--debug", + "--no-tests", + "configure:arch" + ], + "presentation": { + "clear": true + }, + "group": "build", + "detail": "configure core" + }, { "type": "shell", "label": "core: build", "command": "${workspaceFolder}/core/build.sh", "args": [ - "configure:arch", - "build:arch", - "--debug" + "--debug", + "--no-tests", + "build:arch" ], - "options": { - "cwd": "${workspaceFolder}/core" - }, "problemMatcher": [ { "owner": "cpp", @@ -49,70 +72,6 @@ "group": "build", "detail": "build core" }, - { - "type": "shell", - "label": "core: configure", - "command": "${workspaceFolder}/core/build.sh", - "args": [ "--debug", "configure", - ], - "options": { - "cwd": "${workspaceFolder}/core", - }, - "presentation": { - "clear": true - }, - "group": "build", - "detail": "configure core" - }, - { - "type": "shell", - "label": "core: configure+build", - "command": "${workspaceFolder}/core/build.sh", - "args": [ - "--debug", "configure", "build", "tests" - ], - "options": { - "cwd": "${workspaceFolder}/core" - }, - "problemMatcher": [ - { - "owner": "cpp", - "source": "compiler", - "fileLocation": [ - "relative", - "${workspaceFolder}/core/" - ], - "pattern": [ - { - "regexp": "^../../../(.*):(\\d+):(\\d+):\\s+(warning|error|note):\\s+(.*)(\\s+\\[.*\\])?$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - } - ] - }, - { - "owner": "cpp", - "source": "linker", - "fileLocation": "absolute", - "pattern": [ - { - "regexp": "^(/usr/bin/ld:\\s+)?(.*):(\\d+):\\s+(.*)$", - "file": 2, - "location": 3, - "message": 4 - } - ] - }, - ], - "presentation": { - "clear": true - }, - "group": "build", - "detail": "build core" - }, { "type": "shell", "label": "core: test", diff --git a/ios/engine/KMEI/KeymanEngine/ckl.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/ckl.lproj/Localizable.strings index 3156ac7c36..5a43cc3933 100644 --- a/ios/engine/KMEI/KeymanEngine/ckl.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/ckl.lproj/Localizable.strings @@ -125,7 +125,7 @@ "kmp-error-missing-resource" = "Package ǝni adiyaka kibod ko dictionary namtǝ gǝ yuwɛ wɛ."; /* Error installing a Keyman package with a version of Keyman that does not support it */ -"kmp-error-unsupported-keyman-version" = "This package requires a newer version of Keyman."; +"kmp-error-unsupported-keyman-version" = "Pakeji ǝni ada kǝthlǝr aka jiliyir Keyman nam bǝlin."; /* Error opening a Keyman package - cannot parse contents */ "kmp-error-no-metadata" = "Package ǝni ndadi tsati tǝni kal kal wɛ - suyere nam akwa ndadi zǝndi wɛ."; diff --git a/linux/debian/changelog b/linux/debian/changelog index 3eef5dcfa7..9e6e7f550c 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,17 @@ +keyman (16.0.139-2) unstable; urgency=medium + + * Don't build on s390x because Keyman doesn't work on big-endian architectures + (upstream bug https://github.com/keymanapp/keyman/issues/5111) + + -- Eberhard Beilharz Mon, 20 Mar 2023 19:54:44 +0100 + +keyman (16.0.139-1) unstable; urgency=medium + + * New upstream release. + * Re-release to Debian + + -- Eberhard Beilharz Thu, 16 Mar 2023 08:59:04 +0100 + keyman (16.0.138-4) unstable; urgency=medium * Team upload diff --git a/linux/debian/control b/linux/debian/control index 33f50a90dc..58bb436217 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -105,7 +105,7 @@ Description: Keyman for Linux configuration information about Keyman keyboard packages. Package: libkmnkbp-dev -Architecture: any +Architecture: amd64 arm64 armel armhf i386 mipsel mips64el ppc64el riscv64 Section: libdevel Depends: libkmnkbp0-0 (= ${binary:Version}), @@ -129,7 +129,7 @@ Description: Development files for Keyman keyboard processing library This package contains development headers and libraries. Package: libkmnkbp0-0 -Architecture: any +Architecture: amd64 arm64 armel armhf i386 mipsel mips64el ppc64el riscv64 Section: libs Pre-Depends: ${misc:Pre-Depends}, @@ -155,7 +155,7 @@ Description: Keyman keyboard processing library and applies rules from compiled Keyman keyboard files. Package: ibus-keyman -Architecture: any +Architecture: amd64 arm64 armel armhf i386 mipsel mips64el ppc64el riscv64 Depends: ibus (>= 1.3.7), sudo, diff --git a/linux/ibus-keyman/build.sh b/linux/ibus-keyman/build.sh index c2f71ba9a4..4eed1e2805 100755 --- a/linux/ibus-keyman/build.sh +++ b/linux/ibus-keyman/build.sh @@ -18,11 +18,8 @@ builder_describe \ "build" \ "test" \ "install install artifacts" \ - "uninstall uninstall artifacts" - -# We can't yet depend on core until it moved to the new build.sh syntax -# (currently it doesn't know some parameters that we're passing) -# "@/core configure build" + "uninstall uninstall artifacts" \ + "@/core:arch" builder_parse "$@" diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index 0caa9ac0fc..fcf8eabc5d 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -281,8 +281,11 @@ class InstallKmp(): def extract_kmp(kmpfile, directory): - with zipfile.ZipFile(kmpfile, "r") as zip_ref: - zip_ref.extractall(directory) + try: + with zipfile.ZipFile(kmpfile, "r") as zip_ref: + zip_ref.extractall(directory) + except zipfile.BadZipFile as e: + raise InstallError(InstallStatus.Abort, e) def process_keyboard_data(keyboardID, packageDir) -> None: diff --git a/linux/keyman-config/keyman_config/install_window.py b/linux/keyman-config/keyman_config/install_window.py index 9ae0d41cc0..b0684d79f9 100755 --- a/linux/keyman-config/keyman_config/install_window.py +++ b/linux/keyman-config/keyman_config/install_window.py @@ -49,6 +49,7 @@ class InstallKmpWindow(Gtk.Dialog): def __init__(self, kmpfile, viewkmp=None, language=None): logging.debug("InstallKmpWindow: kmpfile: %s", kmpfile) + self.is_error = False self.kmpfile = kmpfile self.viewwindow = viewkmp self.accelerators = None @@ -67,7 +68,13 @@ class InstallKmpWindow(Gtk.Dialog): mainhbox = Gtk.Box() with tempfile.TemporaryDirectory() as tmpdirname: - extract_kmp(kmpfile, tmpdirname) + try: + extract_kmp(kmpfile, tmpdirname) + except InstallError as e: + self._handle_install_error(e, kmpfile) + self.is_error = True + return + info, system, options, keyboards, files = get_metadata(tmpdirname) if not keyboards: # Likely not a keyboard .kmp file @@ -357,27 +364,30 @@ class InstallKmpWindow(Gtk.Dialog): dialog.run() dialog.destroy() except InstallError as e: - if e.status == InstallStatus.Abort: - message = _("Keyboard {name} could not be installed.").format(name=self.kbname) \ - + "\n\n" + _("Error Message:") + "\n %s" % (e.message) - logging.error(message) - message_type = Gtk.MessageType.ERROR - else: - message = _("Keyboard {name} could not be installed.").format(name=self.kbname) \ - + "\n\n" + _("Warning Message:") + "\n %s" % (e.message) - logging.warning(message) - message_type = Gtk.MessageType.WARNING - dialog = Gtk.MessageDialog( - self, 0, message_type, - Gtk.ButtonsType.OK, message) - dialog.run() - dialog.destroy() + self._handle_install_error(e, self.kbname) self.close() def on_cancel_clicked(self, button): logging.info("Cancel install keyboard") self.response(Gtk.ResponseType.CANCEL) + def _handle_install_error(self, e, kbname): + if e.status == InstallStatus.Abort: + message = _("Keyboard {name} could not be installed.").format(name=kbname) \ + + "\n\n" + _("Error Message:") + "\n %s" % (e.message) + logging.error(message) + message_type = Gtk.MessageType.ERROR + else: + message = _("Keyboard {name} could not be installed.").format(name=kbname) \ + + "\n\n" + _("Warning Message:") + "\n %s" % (e.message) + logging.warning(message) + message_type = Gtk.MessageType.WARNING + dialog = Gtk.MessageDialog( + self, 0, message_type, + Gtk.ButtonsType.OK, message) + dialog.run() + dialog.destroy() + def main(argv): if len(sys.argv) != 2: diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 671ca2d279..f00174b5e7 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -82,6 +82,8 @@ class ViewInstalledWindowBase(Gtk.Window): def install_file(self, kmpfile, language=None): installDlg = InstallKmpWindow(kmpfile, viewkmp=self, language=language) + if installDlg.is_error: + return Gtk.ResponseType.CANCEL result = installDlg.run() installDlg.destroy() return result diff --git a/linux/keyman-config/locale/ckl_NG.po b/linux/keyman-config/locale/ckl_NG.po index b4cacac338..8dab46ae85 100644 --- a/linux/keyman-config/locale/ckl_NG.po +++ b/linux/keyman-config/locale/ckl_NG.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: keyman\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-08-19 19:17+0200\n" -"PO-Revision-Date: 2022-10-25 04:29\n" +"PO-Revision-Date: 2023-03-16 04:24\n" "Last-Translator: \n" "Language-Team: Kibaku\n" "Language: ckl_NG\n" diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/ckl.lproj/KMAboutWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/ckl.lproj/KMAboutWindowController.strings new file mode 100644 index 0000000000..1f4ab4ec7a --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/ckl.lproj/KMAboutWindowController.strings @@ -0,0 +1,8 @@ +/* button text to close the About window */ +"Kab-Up-fYH.title" = "Harti"; + +/* text of link to the license agreement */ +"hYC-Bx-aoP.title" = "Lasis nǝr ʻYa suyere"; + +/* button text to open Configuration window */ +"vkh-b5-vO7.title" = "Whalir ʻya suyere wato Konfigareshon..."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/ckl.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/ckl.lproj/preferences.strings new file mode 100644 index 0000000000..396fccf748 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/ckl.lproj/preferences.strings @@ -0,0 +1,38 @@ +/* Checkbox text to always show on-screen keyboard */ +"4UX-80-0Vo.title" = "Ka dzǝ tsanti ata skrin nǝr kibod kǝlara saka"; + +/* Checkbox text to enable verbose Console logging */ +"7J6-zy-R20.title" = "Kǝthlǝr aka verbos Konsol akwa sakar lukwa layir sambar"; + +/* Support button text */ +"93O-x6-RLF.label" = "Sǝr dlarɛ"; + +/* Download Keyboard button text */ +"CTw-kf-WNS.title" = "Sǝgǝl aka Kibod..."; + +/* Keyman Configuration window title */ +"F0z-JX-Cv5.title" = "Su namtǝ ndǝ ɗǝmɦɛ ŋǝr Keyman"; + +/* button text to go back to previous page */ +"JOK-JV-n8w.title" = "Mwari tubwa hi"; + +/* Keyboards button text */ +"MPN-9N-wWc.label" = "Kibodyere"; + +/* text to explain verbose Console logging option */ +"MrI-GM-7d6.title" = "Ma saka namtǝ verbos logging ada kǝthlǝr, Keyman ata kǝthlǝr akwa whalɛ namtǝ sǝr dlarǝrniyere ata ʻya ka zǝndiba damicinyere nam adai. Ndata mǝzi kǝthlǝr aka Program nǝr Klnsol ka ndǝ wuti labaryere nǝr Keyman nam akwa Klnsole, ka ndǝ tsanti shanga labaryere nam akwa kuta Keyman. Ndata mǝzi hyanba shanga labaryere ǝni alukwa dlarǝr Keyman ma ndɛ ada bara."; + +/* button text to move forward to the next page */ +"eXr-8V-h1g.title" = "Alǝkǝma"; + +/* button text to return to the home help page */ +"fXS-aC-CMH.title" = "Livi ko alivi"; + +/* Options button text */ +"frd-No-seV.label" = "Sǝr tsarba ka ʻyayere"; + +/* Installed Keyboard column heading */ +"jex-Nd-Qzg.headerCell.title" = "Hau ko vu kibod"; + +/* Verbose logging tooltip text */ +"uWx-3J-U0D.ibShadowedToolTip" = "Hyanti su ǝni ma gaka damicinidzi laka aka kibod laka ko aka kǝlara Keyman laka."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/ckl.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/ckl.lproj/KMInfoWindowController.strings new file mode 100644 index 0000000000..f3c1994455 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/ckl.lproj/KMInfoWindowController.strings @@ -0,0 +1,8 @@ +/* Package Information window title */ +"F0z-JX-Cv5.title" = "Tsabtǝdzǝr Sǝr Zǝndiyere"; + +/* button text to show Details */ +"Fvy-XJ-s38.label" = "Suyere nam akuyaka nji zǝndi su ata kǝryere"; + +/* button text to show Read Me */ +"waA-IW-Qyn.label" = "Kǝratu Tǝra"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/ckl.lproj/KMKeyboardHelpWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/ckl.lproj/KMKeyboardHelpWindowController.strings new file mode 100644 index 0000000000..1cd0e83722 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/ckl.lproj/KMKeyboardHelpWindowController.strings @@ -0,0 +1,8 @@ +/* Keyboard Help window title */ +"F0z-JX-Cv5.title" = "Dlarɛ nǝr Kibod"; + +/* OK button text */ +"Zla-QF-m0K.title" = "Kal kal"; + +/* Print button text */ +"fYY-Uj-y4S.title" = "Print..."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/Localizable.strings new file mode 100644 index 0000000000..20f4336821 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/Localizable.strings @@ -0,0 +1,71 @@ +/* Message displayed to confirm delete of the Keyman keyboard selected by the user from the keyboard list */ +"message-confirm-delete-keyboard" = "Gada bara kǝlba kǝlba ka ɗalba kibod '%@' ya?"; + +/* Delete keyboard confirmation info indicating that the delete action cannot be undone */ +"info-cannot-undo-delete-keyboard" = "Gadiyata mǝzi ka pali su namtǝ gǝ kurta ʻyati wɛ."; + +/* Button text to cancel delete of the specified installed Keyman keyboard */ +"button-cancel-delete-keyboard" = "Kansǝl"; + +/* Button text to confirm delete of the specified installed Keyman keyboard */ +"button-delete-keyboard" = "Kǝlba ka ɗalba"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"message-confirm-install-keyboard" = "Vu Keyman kibod?"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"info-install-keyboard-filename" = "Gada bara ka Keyman vu alagɛ kibod nam akwa kuta fail ya '%@'?"; + +/* Button text to cancel installation of the double-clicked Keyman file */ +"button-cancel-install-keyboard" = "Kansǝl"; + +/* Button text to confirm installation of the double-clicked Keyman file */ +"button-install-keyboard" = "Vu ko hau"; + +/* Message displayed to inform user that .kmp file could not be read/unzipped */ +"message-keyboard-file-unreadable" = "Ta mǝzi ka karatɛ fail nǝr Keyman wɛ '%@'."; + +/* Message displayed in Configuration window when keyboard cannot be loaded */ +"message-error-loading-keyboard" = "(aibe akwa vu kibod)"; + +/* Message displayed in Configuration window when keyboard metadata cannot be loaded */ +"message-error-unknown-metadata" = "ndǝta zǝndi suyari wɛ"; + +/* Button text to acknowledge that .kmp file could not be read */ +"button-keyboard-file-unreadable" = "Kal kal"; + +/* label text to identify Keyman version */ +"version-label-text" = "Jiliyari %@"; + +/* Status text for keyboard downloading */ +"message-keyboard-downloading" = "Ata kǝra sǝgǝl akǝr..."; + +/* Status text for keyboard download complete */ +"message-keyboard-download-complete" = "Hau ko sǝgǝl aka suyari kǝ kuri."; + +/* Button text to cancel downloading keyboard */ +"button-cancel-downloading" = "Kansǝl"; + +/* Button text keyboard download complete */ +"button-download-complete" = "Kurai ʻyatidzi"; + +/* keyboards label in the Package Information window */ +"keyboards-label" = "Kibodyere:"; + +/* fonts label in the Package Information window */ +"fonts-label" = "Fontyere:"; + +/* package version label in the Package Information window */ +"package-version-label" = "Jiliyir tsabtǝdzǝr failyere nam ada mǝrti su apa kibod namtǝ ndɛ ada na njǝr kuthlǝr aka kamputa:"; + +/* author label in the Package Information window */ +"author-label" = "Ndǝ nam tara ʻyati sari:"; + +/* website label in the Package Information window */ +"website-label" = "Websait:"; + +/* copyright label in the Package Information window */ +"copyright-label" = "Kopirait:"; + +/* message displayed to alert user to need grant accessibility permission */ +"privacy-alert-text" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Preferences, Security & Privacy.\nRestart your system."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/MainMenu.strings b/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/MainMenu.strings new file mode 100644 index 0000000000..451ab27f73 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/ckl.lproj/MainMenu.strings @@ -0,0 +1,14 @@ +/* Configuration window menu text */ +"P1b-lE-yFw.title" = "Whalǝr ʻya suyere ani konfigareshon..."; + +/* Keyboards menu text */ +"bQa-j9-nHe.title" = "Kibodyere"; + +/* Keyboards menu text */ +"goP-aK-3WB.title" = "Kibodyere"; + +/* About menu text */ +"kb2-ww-RS3.title" = "Ata kǝra"; + +/* On-Screen Keyboard menu text */ +"s96-JI-YDh.title" = "On Skrin Kibod"; diff --git a/resources/build/build-utils.md b/resources/build/build-utils.md index 5c3897e36b..16e1e7fce8 100644 --- a/resources/build/build-utils.md +++ b/resources/build/build-utils.md @@ -125,6 +125,10 @@ variable to ensure that we run the correct versions of npm package commands, so there is no need to hard-code path references or add script wrappers to package.json (`npm run