From 8e833c8314be4b98503b22ba5f07de59d59573bc Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Mon, 13 Jan 2025 14:08:22 +0700 Subject: [PATCH 1/9] chore(mac): reworked sentry testing can now force crashes or send messages by typing a single character when sentry testing is enabled in the UserDefaults --- .../Keyman4MacIM/KMInputController.m | 4 +- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 1 + .../Keyman4MacIM/KMInputMethodAppDelegate.m | 40 ++++++++++- .../Keyman4MacIM/KMInputMethodEventHandler.m | 67 ++++++++----------- .../Keyman4MacIM/KMInputMethodLifecycle.h | 3 +- .../Keyman4MacIM/KMInputMethodLifecycle.m | 5 +- 6 files changed, 74 insertions(+), 46 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m index e97be3e389c..acefcc84170 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m @@ -25,7 +25,7 @@ - (KMInputMethodAppDelegate *)appDelegate { - (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)inputClient { - os_log_debug([KMLogs lifecycleLog], "initWithServer, active app: '%{public}@'", [KMInputMethodLifecycle getClientApplicationId]); + os_log_debug([KMLogs lifecycleLog], "initWithServer, active app: '%{public}@'", [KMInputMethodLifecycle getRunningApplicationId]); self = [super initWithServer:server delegate:delegate client:inputClient]; if (self) { @@ -84,7 +84,7 @@ - (void)inputMethodChangedClient:(NSNotification *)notification { if (_eventHandler != nil) { [_eventHandler deactivate]; } - _eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:[KMInputMethodLifecycle getClientApplicationId] client:self.client]; + _eventHandler = [[KMInputMethodEventHandler alloc] initWithClient:[KMInputMethodLifecycle getRunningApplicationId] client:self.client]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index fe06fa603a8..d41bf258999 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -124,6 +124,7 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent; - (KeymanVersionInfo)versionInfo; - (void)registerConfigurationWindow:(NSWindowController *)window; +- (BOOL)isSentryTestingEnabled; @end #endif /* KMInputMethodAppDelegate_h */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index b938279197b..9a5a98de473 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -48,6 +48,7 @@ @interface KMInputMethodAppDelegate () @property (nonatomic, strong) KMPackageReader *packageReader; @property BOOL receivedKeyDownFromOsk; @property NSEventModifierFlags oskEventModifiers; +@property (nonatomic, assign) BOOL sentryTestingEnabled; @end @implementation KMInputMethodAppDelegate @@ -143,11 +144,18 @@ - (void)inputMethodActivated:(NSNotification *)notification { os_log_debug([KMLogs lifecycleLog], "***KMInputMethodAppDelegate inputMethodActivated, re-enabling event tap..."); CGEventTapEnable(self.lowLevelEventTap, YES); } - + if (_kvk != nil && ([KMInputMethodLifecycle.shared shouldShowOskOnActivate])) { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodActivated, showing OSK"); [self showOSK]; } + + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelInfo; + crumb.category = @"startup"; + //crumb.message = [NSString stringWithFormat:@"Authenticated user %@", user.email]; + crumb.message = [NSString stringWithFormat:@"inputMethodActivated"]; + [SentrySDK addBreadcrumb:crumb]; } - (KeymanVersionInfo)versionInfo { @@ -181,6 +189,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; [self setPostLaunchKeymanSentryTags]; + self.sentryTestingEnabled = [KMSettingsRepository.shared readForceSentryError]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; } @@ -196,6 +205,29 @@ - (void)startSentry { }]; } +/** + * Determine whether sentry testing is enabled. + * When it is enabled, then typing certain characters will cause Sentry events or crashes to occur. + * + * To enable, set the flag is UserDefaults + * `defaults write keyman.inputmethod.Keyman KMForceSentryError 1` + * Also, use the SIL Euro Latin keyboard and type in the Stickies app + */ +- (BOOL)isSentryTestingEnabled { + NSString* const testKeyboardName = @"sil_euro_latin.kmx"; + NSString* const testClientApplicationId = @"com.apple.Stickies"; + BOOL testingEnabled = NO; + + if (self.sentryTestingEnabled) { + NSString * kmxName = [[self.kmx filePath] lastPathComponent]; + NSString * applicationId = [KMInputMethodLifecycle.shared clientApplicationId]; + testingEnabled = [kmxName isEqualToString:testKeyboardName] && [applicationId isEqualToString:testClientApplicationId]; + os_log_info([KMLogs testLog], "isSentryTestingEnabled, self.sentryTestingEnabled: %{public}@ kmxName: %{public}@ applicationId: %{public}@ testingEnabled: %{public}@", self.sentryTestingEnabled?@"YES":@"NO", kmxName, applicationId, testingEnabled?@"YES":@"NO"); + } + + return testingEnabled; +} + - (void)setPostLaunchKeymanSentryTags { NSString *keyboardFileName = [self.kmx.filePath lastPathComponent]; os_log_info([KMLogs keyboardLog], "initial kmx set to %{public}@", keyboardFileName); @@ -825,6 +857,12 @@ - (void) setDefaultSelectedKeyboard { [self setSelectedKeyboard:keyboardName inMenuItem:menuItem]; [self setSelectedKeyboard:keyboardName]; [self setContextBuffer:nil]; + + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelInfo; + crumb.category = @"startup"; + crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; + [SentrySDK addBreadcrumb:crumb]; } - (void) addKeyboardPlaceholderMenuItem { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index 2e38dd4bc3a..91ebe13f33f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -22,6 +22,7 @@ @interface KMInputMethodEventHandler () @property (nonatomic, retain) NSString* clientApplicationId; @property NSString *queuedText; @property BOOL contextChanged; +@property BOOL sentryTestingEnabled; @end @implementation KMInputMethodEventHandler @@ -33,9 +34,6 @@ @implementation KMInputMethodEventHandler CGKeyCode _keyCodeOfOriginalEvent; CGEventSourceRef _sourceFromOriginalEvent = nil; CGEventSourceRef _sourceForGeneratedEvent = nil; -NSMutableString* _easterEggForSentry = nil; -NSString* const kEasterEggText = @"Sentry force now"; -NSString* const kEasterEggKmxName = @"EnglishSpanish.kmx"; // This is the public initializer. - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { @@ -44,20 +42,7 @@ - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _generatedBackspaceCount = 0; _lowLevelBackspaceCount = 0; _queuedText = nil; - - BOOL forceSentryCrash = [KMSettingsRepository.shared readForceSentryError]; - - // In Xcode, if Keyman is the active IM and the settings include the - // forceSentryCrash flag and "English plus Spanish" is the current keyboard - // and you type "Sentry force now", it will force a simulated crash to - // test reporting to sentry.keyman.com - if (forceSentryCrash && [clientAppId isEqual: @"com.apple.dt.Xcode"]) { - os_log_debug([KMLogs testLog], "initWithClient, preparing to force Sentry crash."); - _easterEggForSentry = [[NSMutableString alloc] init]; - } - else - _easterEggForSentry = nil; - + [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { [scope setTagValue:clientAppId forKey:@"clientAppId"]; }]; @@ -110,9 +95,15 @@ - (KMEngine *)kme { - (BOOL) handleEventWithKeymanEngine:(NSEvent *)event in:(id) sender { CoreKeyOutput *output = nil; + if ([self.appDelegate isSentryTestingEnabled]) { + if ([self forceSentryEvent:event]) { + return NO; + } + } + output = [self processEventWithKeymanEngine:event in:sender]; + if (output == nil) { - [self checkEventForSentryEasterEgg:event]; return NO; } @@ -139,28 +130,26 @@ - (CoreKeyOutput*) processEventWithKeymanEngine:(NSEvent *)event in:(id) sender return coreKeyOutput; } -- (void)checkEventForSentryEasterEgg:(NSEvent *)event { - if (_easterEggForSentry != nil) { - NSString * kmxName = [[self.kme.kmx filePath] lastPathComponent]; - os_log_debug([KMLogs testLog], "Sentry - KMX name: %{public}@", kmxName); - if ([kmxName isEqualToString:kEasterEggKmxName]) { - NSUInteger len = [_easterEggForSentry length]; - os_log_debug([KMLogs testLog], "Sentry - Processing character(s): %{public}@", [event characters]); - if ([[event characters] characterAtIndex:0] == [kEasterEggText characterAtIndex:len]) { - NSString *characterToAdd = [kEasterEggText substringWithRange:NSMakeRange(len, 1)]; - os_log_debug([KMLogs testLog], "Sentry - Adding character to Easter Egg code string: %{public}@", characterToAdd); - [_easterEggForSentry appendString:characterToAdd]; - if ([_easterEggForSentry isEqualToString:kEasterEggText]) { - os_log_debug([KMLogs testLog], "Sentry - Forcing crash now"); - [SentrySDK crash]; - } - } - else if (len > 0) { - os_log_debug([KMLogs testLog], "Sentry - Clearing Easter Egg code string."); - [_easterEggForSentry setString:@""]; - } - } +/** + * When in the mode to test sentry, this method will force certain Sentry APIs to be called, + * such as forcing a crash or capturing a message, depending on the key being typed. + */ +- (BOOL)forceSentryEvent:(NSEvent *)event { + BOOL forcedEvent = YES; + + NSString *sentryCommand = event.characters; + if ([sentryCommand isEqualToString:@"{"]) { + os_log_debug([KMLogs testLog], "forceSentryEvent: forcing crash now"); + [SentrySDK crash]; + } else if ([sentryCommand isEqualToString:@"["]) { + os_log_debug([KMLogs testLog], "forceSentryEvent: forcing message now"); + [SentrySDK captureMessage:@"Forced test message"]; + } else { + os_log_debug([KMLogs testLog], "forceSentryEvent: unrecognized command"); + forcedEvent = NO; } + + return forcedEvent; } /** diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.h index 62a3aac35d1..e467e645bd7 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.h @@ -15,7 +15,8 @@ extern NSString *const kInputMethodClientChangeNotification; @interface KMInputMethodLifecycle : NSObject + (KMInputMethodLifecycle *)shared; -+ (NSString*)getClientApplicationId; ++ (NSString*)getRunningApplicationId; +@property NSString *clientApplicationId; - (void)startLifecycle; - (void)activateClient:(id)client; - (void)deactivateClient:(id)client; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m index 9b877a530e7..0b82c830061 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m @@ -61,7 +61,6 @@ @interface KMInputMethodLifecycle() @property LifecycleState lifecycleState; @property NSString *inputSourceId; -@property NSString *clientApplicationId; @end @implementation KMInputMethodLifecycle @@ -106,7 +105,7 @@ + (NSString*)getCurrentInputSourceId { /** * Get the bundle ID of the currently active text input client.. */ -+ (NSString*)getClientApplicationId { ++ (NSString*)getRunningApplicationId { NSRunningApplication *currentApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; NSString *clientAppId = [currentApp bundleIdentifier]; return clientAppId; @@ -156,7 +155,7 @@ - (void)saveNewInputMethodState:(NSString*)newInputSourceId withAppId:(NSString* */ - (void)performTransition:(id)client { NSString *currentInputSource = [KMInputMethodLifecycle getCurrentInputSourceId]; - NSString *currentClientAppId = [KMInputMethodLifecycle getClientApplicationId]; + NSString *currentClientAppId = [KMInputMethodLifecycle getRunningApplicationId]; TransitionType transition = [self determineTransition:currentInputSource withAppId:currentClientAppId]; [self saveNewInputMethodState:currentInputSource withAppId:currentClientAppId]; From 76d0d69e6b5e42a3e27a4ff6ed305667ee83cf95 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Thu, 16 Jan 2025 15:26:03 +0700 Subject: [PATCH 2/9] chore(mac): add KMSentryHelper class, and new tags and breadcrumbs --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 18 ++++- .../KMConfigurationWindowController.m | 33 ++++++-- .../KMDownloadKBWindowController.m | 1 + .../Keyman4MacIM/KMInputController.m | 6 +- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 2 +- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 74 ++++++++++-------- .../Keyman4MacIM/KMInputMethodEventHandler.m | 7 +- .../Keyman4MacIM/KMInputMethodLifecycle.m | 8 +- .../Keyman4MacIM/{ => Logging}/KMLogs.h | 3 +- .../Keyman4MacIM/{ => Logging}/KMLogs.m | 2 +- .../Keyman4MacIM/Logging/KMSentryHelper.h | 25 ++++++ .../Keyman4MacIM/Logging/KMSentryHelper.m | 77 +++++++++++++++++++ .../OnScreenKeyboard/OSKWindowController.m | 2 + .../Keyman4MacIM/TextApiCompliance.m | 13 ++++ 14 files changed, 217 insertions(+), 54 deletions(-) rename mac/Keyman4MacIM/Keyman4MacIM/{ => Logging}/KMLogs.h (84%) rename mac/Keyman4MacIM/Keyman4MacIM/{ => Logging}/KMLogs.m (99%) create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 483998a16e0..3436ed498a3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 29BE9D872CA3C21900B67DE7 /* KMModifierMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */; }; 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; + 29DBBF672D35053E00D8E33C /* KMSentryHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 29DBBF662D35053E00D8E33C /* KMSentryHelper.m */; }; 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */ = {isa = PBXBuildFile; fileRef = 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; @@ -275,6 +276,8 @@ 29D470972C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/KMKeyboardHelpWindowController.strings; sourceTree = "<group>"; }; 29D470982C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/MainMenu.strings; sourceTree = "<group>"; }; 29D470992C648D7100224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; }; + 29DBBF652D35053E00D8E33C /* KMSentryHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMSentryHelper.h; sourceTree = "<group>"; }; + 29DBBF662D35053E00D8E33C /* KMSentryHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMSentryHelper.m; sourceTree = "<group>"; }; 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = SILAndikaV1RGB.png; sourceTree = "<group>"; }; 29DD8400276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/KMAboutWindowController.strings; sourceTree = "<group>"; }; 29DD8401276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/preferences.strings; sourceTree = "<group>"; }; @@ -433,6 +436,17 @@ path = Privacy; sourceTree = "<group>"; }; + 29DBBF642D34FFE000D8E33C /* Logging */ = { + isa = PBXGroup; + children = ( + 29B4A0D42BF7675A00682049 /* KMLogs.h */, + 29B4A0D32BF7675A00682049 /* KMLogs.m */, + 29DBBF652D35053E00D8E33C /* KMSentryHelper.h */, + 29DBBF662D35053E00D8E33C /* KMSentryHelper.m */, + ); + path = Logging; + sourceTree = "<group>"; + }; 2CDC487CDFFBB26D621D73BD /* Pods */ = { isa = PBXGroup; children = ( @@ -564,8 +578,6 @@ 989C9C0A1A7876DE00A20425 /* Keyman4MacIM */ = { isa = PBXGroup; children = ( - 29B4A0D42BF7675A00682049 /* KMLogs.h */, - 29B4A0D32BF7675A00682049 /* KMLogs.m */, 299ABD6F29ECE75B00AA5948 /* KeySender.m */, 299ABD7029ECE75B00AA5948 /* KeySender.h */, D861B03D2C5747F70003675E /* KMSettingsRepository.h */, @@ -575,6 +587,7 @@ 297A501128DF4D360074EB1B /* Privacy */, 98FE105B1B4DE86300525F54 /* Categories */, 98D6DA791A799EE700B09822 /* Frameworks */, + 29DBBF642D34FFE000D8E33C /* Logging */, 989C9C121A7876DE00A20425 /* Images.xcassets */, 98BDD3681BC3511200FAC7C4 /* Keyman4MacIM.entitlements */, 98BF92401BF01CBE0002126A /* KMAboutWindow */, @@ -1000,6 +1013,7 @@ 9A3D6C5D221531B0008785A3 /* KMOSVersion.m in Sources */, 989C9C111A7876DE00A20425 /* main.m in Sources */, 290BC680274B9DB1005CD1C3 /* KMPackageInfo.m in Sources */, + 29DBBF672D35053E00D8E33C /* KMSentryHelper.m in Sources */, 29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */, 98BF924F1BF02DC20002126A /* KMBarView.m in Sources */, E240F599202DED740000067D /* KMPackage.m in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 202be65ef5e..142606152fa 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -10,6 +10,7 @@ #import "KMDownloadKBWindowController.h" #import "KMDataRepository.h" #import "KMLogs.h" +#import "KMSentryHelper.h" @interface KMConfigurationWindowController () @property (nonatomic, weak) IBOutlet NSTableView *tableView; @@ -363,18 +364,23 @@ - (void)checkBoxAction:(id)sender { NSString *partialPath = [KMDataRepository.shared trimToPartialPath:kmxFilePath]; os_log_debug([KMLogs uiLog], "checkBoxAction, kmxFilePath = %{public}@ for checkBox.tag %li, partialPath = %{public}@", kmxFilePath, checkBox.tag, partialPath); if (checkBox.state == NSOnState) { - os_log_debug([KMLogs uiLog], "Adding active keyboard: %{public}@", kmxFilePath); + os_log_debug([KMLogs uiLog], "enabling active keyboard: %{public}@", kmxFilePath); + NSString *message = [NSString stringWithFormat:@"enabling active keyboard: %@", kmxFilePath]; + [KMSentryHelper addUserBreadCrumb:@"config" message:message]; [self.activeKeyboards addObject:partialPath]; [self saveActiveKeyboards]; } else if (checkBox.state == NSOffState) { - os_log_debug([KMLogs uiLog], "Disabling active keyboard: %{public}@", kmxFilePath); + os_log_debug([KMLogs uiLog], "disabling active keyboard: %{public}@", kmxFilePath); + NSString *message = [NSString stringWithFormat:@"disabling active keyboard: %@", kmxFilePath]; + [KMSentryHelper addUserBreadCrumb:@"config" message:message]; [self.activeKeyboards removeObject:partialPath]; [self saveActiveKeyboards]; } } - (void)infoAction:(id)sender { + [KMSentryHelper addUserBreadCrumb:@"user" message:@"getting package information"]; NSButton *infoButton = (NSButton *)sender; NSString *packagePath = [self packagePathAtIndex:infoButton.tag]; if (packagePath != nil) { @@ -389,6 +395,7 @@ - (void)infoAction:(id)sender { } - (void)helpAction:(id)sender { + [KMSentryHelper addUserBreadCrumb:@"user" message:@"displaying help"]; NSButton *helpButton = (NSButton *)sender; NSString *packagePath = [self packagePathAtIndex:helpButton.tag]; if (packagePath != nil) { @@ -406,16 +413,25 @@ - (void)removeAction:(id)sender { NSButton *deleteButton = (NSButton *)sender; NSDictionary *info = [self.tableContents objectAtIndex:deleteButton.tag]; NSString *deleteKeyboardMessage = NSLocalizedString(@"message-confirm-delete-keyboard", nil); + NSString *keyboardName = nil; + + if ([info objectForKey:@"HeaderTitle"] != nil) { + keyboardName = [info objectForKey:@"HeaderTitle"]; + [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; + } else { + keyboardName = [info objectForKey:kKMKeyboardNameKey]; + [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; + } - if ([info objectForKey:@"HeaderTitle"] != nil) - [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, [info objectForKey:@"HeaderTitle"]]]; - else - [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, [info objectForKey:kKMKeyboardNameKey]]]; - + os_log_debug([KMLogs testLog], "entered removeAction for keyboardName: %{public}@", keyboardName); + [self.deleteAlertView beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertFirstButtonReturn) { os_log_debug([KMLogs uiLog], "confirm delete keyboard alert dismissed"); [self deleteFileAtIndex:[NSNumber numberWithInteger:deleteButton.tag]]; + + os_log_debug([KMLogs testLog], "removeAction for keyboardName: %{public}@", keyboardName); + [KMSentryHelper addBreadCrumb:@"configure" message:[NSString stringWithFormat:@"remove keyboard: %@", keyboardName]]; } self.deleteAlertView = nil; }]; @@ -453,7 +469,8 @@ - (void)handleRequestToInstallPackage:(KMPackage *) package { - (void)installPackageFile:(NSString *)kmpFile { // kmpFile could be a temp file (in fact, it always is!), so don't display the name. os_log_debug([KMLogs dataLog], "kmpFile - ready to unzip/install Package File: %{public}@", kmpFile); - + [KMSentryHelper addBreadCrumb:@"configure" message:@"install package file"]; + BOOL didUnzip = [self.AppDelegate unzipFile:kmpFile]; if (!didUnzip) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m index 4b5ce44dea8..2fec9f8b320 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m @@ -9,6 +9,7 @@ #import "KMDownloadKBWindowController.h" #import "KMInputMethodAppDelegate.h" #import "KMLogs.h" +#import "KMSentryHelper.h" @interface KMDownloadKBWindowController () @property (nonatomic, weak) IBOutlet WebView *webView; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m index acefcc84170..dd53b496227 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m @@ -13,7 +13,7 @@ #import "KMLogs.h" #import "InputMethodKit/InputMethodKit.h" #import "KMInputMethodLifecycle.h" - +#import "KMSentryHelper.h" @implementation KMInputController @@ -111,17 +111,21 @@ - (void)menuAction:(id)sender { NSInteger itag = mItem.tag; os_log_debug([KMLogs uiLog], "Keyman menu clicked - tag: %lu", itag); if (itag == CONFIG_MENUITEM_TAG) { + [KMSentryHelper addUserBreadCrumb:@"menu" message:@"Configuration..."]; [self showConfigurationWindow:sender]; } else if (itag == OSK_MENUITEM_TAG) { + [KMSentryHelper addUserBreadCrumb:@"menu" message:@"On-screen Keyboard"]; [KMSettingsRepository.shared writeShowOskOnActivate:YES]; os_log_debug([KMLogs oskLog], "menuAction OSK_MENUITEM_TAG, updating settings writeShowOsk to YES"); [self.appDelegate showOSK]; } else if (itag == ABOUT_MENUITEM_TAG) { + [KMSentryHelper addUserBreadCrumb:@"menu" message:@"About"]; [self.appDelegate showAboutWindow]; } else if (itag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) { + [KMSentryHelper addUserBreadCrumb:@"menu" message:@"Selected Keyboard"]; [self.appDelegate selectKeyboardFromMenu:itag]; } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index d41bf258999..eade3d6d2f4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -124,7 +124,7 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent; - (KeymanVersionInfo)versionInfo; - (void)registerConfigurationWindow:(NSWindowController *)window; -- (BOOL)isSentryTestingEnabled; +- (BOOL)canForceSentryEvents; @end #endif /* KMInputMethodAppDelegate_h */ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 9a5a98de473..ad0249643cb 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -17,6 +17,7 @@ #import "KMPackageInfo.h" #import "PrivacyConsent.h" #import "KMLogs.h" +#import "KMSentryHelper.h" @import Sentry; NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification"; @@ -125,10 +126,12 @@ - (void)initCompletion { - (void)inputMethodDeactivated:(NSNotification *)notification { if ([self.oskWindow.window isVisible]) { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, hiding OSK"); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:@"hiding OSK on input method deactivation"]; [self.oskWindow.window setIsVisible:NO]; } else { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, OSK already hidden"); } + [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]?@"true":@"false"]; if (self.lowLevelEventTap) { os_log_debug([KMLogs lifecycleLog], "***inputMethodDeactivated, disabling event tap"); @@ -144,18 +147,14 @@ - (void)inputMethodActivated:(NSNotification *)notification { os_log_debug([KMLogs lifecycleLog], "***KMInputMethodAppDelegate inputMethodActivated, re-enabling event tap..."); CGEventTapEnable(self.lowLevelEventTap, YES); } - + + os_log_debug([KMLogs lifecycleLog], "--- inputMethodActivated, kvk is non-nil: %{public}@ showOskOnActivate: %{public}@", (_kvk!=nil)?@"true":@"false", [KMInputMethodLifecycle.shared shouldShowOskOnActivate]?@"true":@"false"); + if (_kvk != nil && ([KMInputMethodLifecycle.shared shouldShowOskOnActivate])) { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodActivated, showing OSK"); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:@"opening OSK on input method activation"]; [self showOSK]; } - - SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; - crumb.level = kSentryLevelInfo; - crumb.category = @"startup"; - //crumb.message = [NSString stringWithFormat:@"Authenticated user %@", user.email]; - crumb.message = [NSString stringWithFormat:@"inputMethodActivated"]; - [SentrySDK addBreadcrumb:crumb]; } - (KeymanVersionInfo)versionInfo { @@ -206,39 +205,45 @@ - (void)startSentry { } /** - * Determine whether sentry testing is enabled. - * When it is enabled, then typing certain characters will cause Sentry events or crashes to occur. + * Determine whether to force sentry events. + * When true, then typing certain characters will cause Sentry events or crashes to occur. * - * To enable, set the flag is UserDefaults + * To enable, set the flag in UserDefaults * `defaults write keyman.inputmethod.Keyman KMForceSentryError 1` * Also, use the SIL Euro Latin keyboard and type in the Stickies app */ -- (BOOL)isSentryTestingEnabled { +- (BOOL)canForceSentryEvents { NSString* const testKeyboardName = @"sil_euro_latin.kmx"; NSString* const testClientApplicationId = @"com.apple.Stickies"; - BOOL testingEnabled = NO; + BOOL canForce = NO; if (self.sentryTestingEnabled) { NSString * kmxName = [[self.kmx filePath] lastPathComponent]; NSString * applicationId = [KMInputMethodLifecycle.shared clientApplicationId]; - testingEnabled = [kmxName isEqualToString:testKeyboardName] && [applicationId isEqualToString:testClientApplicationId]; - os_log_info([KMLogs testLog], "isSentryTestingEnabled, self.sentryTestingEnabled: %{public}@ kmxName: %{public}@ applicationId: %{public}@ testingEnabled: %{public}@", self.sentryTestingEnabled?@"YES":@"NO", kmxName, applicationId, testingEnabled?@"YES":@"NO"); + canForce = [[self getKmxFileName] isEqualToString:testKeyboardName] && [applicationId isEqualToString:testClientApplicationId]; + os_log_info([KMLogs testLog], "canForceSentryEvents, self.sentryTestingEnabled: %{public}@ kmxName: %{public}@ applicationId: %{public}@ testingEnabled: %{public}@", self.sentryTestingEnabled?@"YES":@"NO", kmxName, applicationId, canForce?@"YES":@"NO"); } - return testingEnabled; + return canForce; +} + +- (NSString*) getKmxFileName { + return [[self.kmx filePath] lastPathComponent]; } - (void)setPostLaunchKeymanSentryTags { NSString *keyboardFileName = [self.kmx.filePath lastPathComponent]; os_log_info([KMLogs keyboardLog], "initial kmx set to %{public}@", keyboardFileName); - NSString *hasAccessibility = [PrivacyConsent.shared checkAccessibility]?@"Yes":@"No"; + NSString *hasAccessibility = [PrivacyConsent.shared checkAccessibility]?@"true":@"false"; - // assign custom keyboard tag in Sentry to initial keyboard - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setTagValue:hasAccessibility forKey:@"accessibilityEnabled"]; - [scope setTagValue:keyboardFileName forKey:@"keyboard"]; - }]; + // assign custom keyboard tag in Sentry for accessibility and initial keyboard + [KMSentryHelper addKeyboardTag:keyboardFileName]; + [KMSentryHelper addHasAccessibilityTag:hasAccessibility]; +// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { +// [scope setTagValue:hasAccessibility forKey:@"accessibilityEnabled"]; +// [scope setTagValue:keyboardFileName forKey:@"keyboard"]; +// }]; } #ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE @@ -438,22 +443,28 @@ - (void)loadKeyboardFromKmxFile:(KMXFile *)kmx { _kmx = kmx; CoreKeyboardInfo *keyboardInfo = [self.kme loadKeyboardFromKmxFile:kmx]; - os_log_info([KMLogs keyboardLog], "setKmx loaded keyboard, keyboard info: %{public}@", keyboardInfo); - + os_log_info([KMLogs keyboardLog], "loadKeyboardFromKmxFile, keyboard info: %{public}@", keyboardInfo); + [KMSentryHelper addBreadCrumb:@"configure" message:[NSString stringWithFormat:@"loadKeyboardFromKmxFile: %@", [self getKmxFileName]]]; + _modifierMapping = [[KMModifierMapping alloc] init:keyboardInfo]; os_log_info([KMLogs keyboardLog], "modifierMapping bothOptionKeysGenerateRightAlt: %d", self.modifierMapping.bothOptionKeysGenerateRightAlt); // assign custom keyboard tag in Sentry to default keyboard - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setTagValue:keyboardInfo.keyboardId forKey:@"keyboard"]; - }]; + [KMSentryHelper addKeyboardTag:keyboardInfo.keyboardId]; } - (void)setKvk:(KVKFile *)kvk { + if (kvk != nil) { + os_log_info([KMLogs lifecycleLog], "*** setKvk, kvk.associatedKeyboard: %{public}@", kvk.associatedKeyboard); + } else { + os_log_info([KMLogs lifecycleLog], "*** setKvk, setting to nil"); + } _kvk = kvk; - if (_oskWindow != nil) + + if (_oskWindow != nil) { [_oskWindow resetOSK]; + } } - (void)setKeyboardName:(NSString *)keyboardName { @@ -858,11 +869,7 @@ - (void) setDefaultSelectedKeyboard { [self setSelectedKeyboard:keyboardName]; [self setContextBuffer:nil]; - SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; - crumb.level = kSentryLevelInfo; - crumb.category = @"startup"; - crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; - [SentrySDK addBreadcrumb:crumb]; + [KMSentryHelper addBreadCrumb:@"startup" message:[NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]]; } - (void) addKeyboardPlaceholderMenuItem { @@ -986,6 +993,7 @@ - (void)showOSK { [[self.oskWindow window] makeKeyAndOrderFront:nil]; [[self.oskWindow window] setLevel:NSStatusWindowLevel]; [[self.oskWindow window] setTitle:self.oskWindowTitle]; + [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]?@"true":@"false"]; } - (void)showAboutWindow { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index 91ebe13f33f..340a06a04eb 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -12,6 +12,7 @@ #import "TextApiCompliance.h" #import "KMSettingsRepository.h" #import "KMLogs.h" +#import "KMSentryHelper.h" @import Sentry; @interface KMInputMethodEventHandler () @@ -43,9 +44,7 @@ - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _lowLevelBackspaceCount = 0; _queuedText = nil; - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setTagValue:clientAppId forKey:@"clientAppId"]; - }]; + [KMSentryHelper addClientAppIdTag:clientAppId]; return self; } @@ -95,7 +94,7 @@ - (KMEngine *)kme { - (BOOL) handleEventWithKeymanEngine:(NSEvent *)event in:(id) sender { CoreKeyOutput *output = nil; - if ([self.appDelegate isSentryTestingEnabled]) { + if ([self.appDelegate canForceSentryEvents]) { if ([self forceSentryEvent:event]) { return NO; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m index 0b82c830061..543d7fe4d8d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m @@ -10,7 +10,7 @@ */ /** - * This class is needed because many activateServer and deactivateServer messages sent from macOS + * This class is needed because many activateServer and deactivateServer messages are sent from macOS * to KMInputController, but they are not particularly reliable. Keyman receives some messages when it * is not active and should not become active. It also receives messages when it is active, but there is no * need to change state. For example, when a menu is clicked with Keyman active, macOS will send a @@ -36,6 +36,7 @@ #import <AppKit/AppKit.h> #import "KMSettingsRepository.h" #import <Carbon/Carbon.h> +#import "KMSentryHelper.h" NSString *const kInputMethodActivatedNotification = @"kInputMethodActivatedNotification"; NSString *const kInputMethodDeactivatedNotification = @"kInputMethodDeactivatedNotification"; @@ -165,7 +166,8 @@ - (void)performTransition:(id)client { os_log_info([KMLogs lifecycleLog], "performTransition: None, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); break; case Activate: - os_log_info([KMLogs lifecycleLog], "performTransition: Activate, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); + os_log_info([KMLogs lifecycleLog], "performTransition: Activate, new application ID: %{public}@", currentClientAppId); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"activated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; /** * Perform two actions when activating the input method. * Change the client first which prepares the event handler. @@ -176,10 +178,12 @@ - (void)performTransition:(id)client { break; case Deactivate: os_log_info([KMLogs lifecycleLog], "performTransition: Deactivate, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"deactivated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; [self deactivateInputMethod]; break; case ChangeClients: os_log_info([KMLogs lifecycleLog], "performTransition: ChangeClients, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"change clients for input method '%@' to application ID '%@'", currentInputSource, currentClientAppId]]; [self changeClient]; break; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMLogs.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h similarity index 84% rename from mac/Keyman4MacIM/Keyman4MacIM/KMLogs.h rename to mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h index 002bc5023c1..f7f83702ec5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMLogs.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMLogs.h @@ -6,7 +6,6 @@ * * Created by Shawn Schantz on 2024-05-16. * - * Contains methods to get singleton logger objects and constants for subsystem and category names. */ #import <Foundation/Foundation.h> diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMLogs.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m similarity index 99% rename from mac/Keyman4MacIM/Keyman4MacIM/KMLogs.m rename to mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m index f2d4dc4d534..6a8a5cd9570 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMLogs.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMLogs.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h new file mode 100644 index 00000000000..e9d6936c0e6 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h @@ -0,0 +1,25 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Created by Shawn Schantz on 2025-01-13. + * + */ + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface KMSentryHelper : NSObject + ++ (void)addApiComplianceTag: (NSString *)value; ++ (void)addOskVisibleTag:(NSString *)value; ++ (void)addClientAppIdTag:(NSString *)value; ++ (void)addKeyboardTag:(NSString *)value; ++ (void)addHasAccessibilityTag:(NSString *)value; ++ (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText; ++ (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText; ++ (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map; + +@end + +NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m new file mode 100644 index 00000000000..5278e36037d --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m @@ -0,0 +1,77 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Created by Shawn Schantz on 2025-01-13. + * + * Description + */ + +#import "KMSentryHelper.h" +@import Sentry; + +@implementation KMSentryHelper + +NSString * const kApiComplianceTagName = @"apiCompliance"; +NSString * const kOskVisibleTagName = @"oskVisible"; +NSString * const kClientAppIdTagName = @"clientAppId"; +NSString * const kKeyboardTagName = @"keyboard"; +NSString * const kHasAccessibilityTagName = @"accessibilityEnabled"; + ++ (void)addApiComplianceTag: (NSString *)value { + [self addCustomTag:kApiComplianceTagName withValue:value]; +} + ++ (void)addOskVisibleTag:(NSString *)value { + [self addCustomTag:kOskVisibleTagName withValue:value]; +} + ++ (void)addClientAppIdTag:(NSString *)value { + [self addCustomTag:kClientAppIdTagName withValue:value]; +} + ++ (void)addKeyboardTag:(NSString *)value { + [self addCustomTag:kKeyboardTagName withValue:value]; +} + ++ (void)addHasAccessibilityTag:(NSString *)value { + [self addCustomTag:kHasAccessibilityTagName withValue:value]; +} + +/** + *assign custom keyboard tag in Sentry to initial keyboard + */ ++ (void)addCustomTag:(NSString *)tagName withValue:(NSString *)value { + [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { + [scope setTagValue:value forKey:tagName]; + }]; +} + ++ (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText { + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelInfo; + crumb.category = category; + crumb.message = messageText; + //crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; + [SentrySDK addBreadcrumb:crumb]; +} + ++ (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText { + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.type = @"user"; + crumb.level = kSentryLevelInfo; + crumb.category = category; + crumb.message = messageText; + //crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; + [SentrySDK addBreadcrumb:crumb]; +} + ++ (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map { + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelInfo; + crumb.category = category; + crumb.message = messageText; + crumb.data = map; + [SentrySDK addBreadcrumb:crumb]; +} + +@end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m index b3824fa8976..df0e723534d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m @@ -11,6 +11,7 @@ #import "KMInputMethodLifecycle.h" #import "KMSettingsRepository.h" #import "KMLogs.h" +#import "KMSentryHelper.h" @interface OSKWindowController () @property (nonatomic, strong) NSButton *helpButton; @@ -76,6 +77,7 @@ - (void)windowWillClose:(NSNotification *)notification { // whenever the OSK is closing clear all of its modifier keys [self.oskView clearOskModifiers]; + [KMSentryHelper addOskVisibleTag:@"false"]; } - (void)helpAction:(id)sender { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m index e83439794b6..4d77bada0f1 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m @@ -36,6 +36,7 @@ #import <InputMethodKit/InputMethodKit.h> #import "KMInputMethodAppDelegate.h" #import "KMLogs.h" +#import "KMSentryHelper.h" // this is the user managed list of non-compliant apps persisted in User Defaults NSString *const kKMLegacyApps = @"KMLegacyApps"; @@ -76,6 +77,16 @@ -(NSString *)description return [NSString stringWithFormat:@"complianceUncertain: %d, hasCompliantSelectionApi: %d, canReadText: %d, canReplaceText: %d, mustBackspaceUsingEvents: %d, clientApplicationId: %@, client: %@", self.complianceUncertain, self.hasCompliantSelectionApi, [self canReadText], [self canReplaceText], [self mustBackspaceUsingEvents], _clientApplicationId, _client]; } +/** + * For Sentry, create a short description as the tag value is limited to 200 characters. + * We don't know how long the clientAppId will be, but this should not be well below 200 characters. + * If the text were to exceed 200 characters, then Sentry would display an error rather than truncating. + */ +-(NSString *)shortSentryTagDescription +{ + return [NSString stringWithFormat:@"uncertain: %@, compliant: %@, clientAppId: %@, ", self.complianceUncertain?@"true":@"false", self.hasCompliantSelectionApi?@"true":@"false", _clientApplicationId]; +} + /** test to see if the API selectedRange functions properly for the text input client */ -(void) checkCompliance:(id) client { // confirm that the API actually exists (this always seems to return true) @@ -91,6 +102,7 @@ -(void) checkCompliance:(id) client { [self checkComplianceUsingInitialSelection]; } os_log_debug([KMLogs complianceLog], "checkCompliance workingSelectionApi for app %{public}@: set to %{public}@", self.clientApplicationId, self.complianceUncertain?@"YES":@"NO"); + [KMSentryHelper addApiComplianceTag:self.shortSentryTagDescription]; } -(void) checkComplianceUsingInitialSelection { @@ -135,6 +147,7 @@ -(void) checkComplianceAfterInsert:(id) client delete:(NSString *)textToDelete i } os_log_info([KMLogs complianceLog], "checkComplianceAfterInsert, self.hasWorkingSelectionApi = %{public}@ for app %{public}@", self.hasCompliantSelectionApi?@"YES":@"NO", self.clientApplicationId); + [KMSentryHelper addApiComplianceTag:self.shortSentryTagDescription]; } - (BOOL)validateNewLocation:(NSUInteger)location delete:(NSString *)textToDelete { From 162dc40f2cea367c0ed298044c4292cc58318094 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Mon, 20 Jan 2025 14:17:15 +0700 Subject: [PATCH 3/9] chore(mac): added architecture tag, more breadcrumbs --- .../KMConfigurationWindowController.m | 17 +++++---- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 38 +++++++++++-------- .../Keyman4MacIM/KMInputMethodEventHandler.m | 8 +++- .../Keyman4MacIM/Logging/KMSentryHelper.h | 6 ++- .../Keyman4MacIM/Logging/KMSentryHelper.m | 18 +++++++-- .../OnScreenKeyboard/OSKWindowController.m | 2 +- 6 files changed, 58 insertions(+), 31 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 142606152fa..6f63bcfccf3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -361,18 +361,19 @@ - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info r - (void)checkBoxAction:(id)sender { NSButton *checkBox = (NSButton *)sender; NSString *kmxFilePath = [self kmxFilePathAtIndex:checkBox.tag]; + NSString * kmxFileName = [kmxFilePath lastPathComponent]; NSString *partialPath = [KMDataRepository.shared trimToPartialPath:kmxFilePath]; os_log_debug([KMLogs uiLog], "checkBoxAction, kmxFilePath = %{public}@ for checkBox.tag %li, partialPath = %{public}@", kmxFilePath, checkBox.tag, partialPath); if (checkBox.state == NSOnState) { - os_log_debug([KMLogs uiLog], "enabling active keyboard: %{public}@", kmxFilePath); - NSString *message = [NSString stringWithFormat:@"enabling active keyboard: %@", kmxFilePath]; + os_log_debug([KMLogs uiLog], "enabling active keyboard: %{public}@", kmxFileName); + NSString *message = [NSString stringWithFormat:@"enabling active keyboard: %@", kmxFileName]; [KMSentryHelper addUserBreadCrumb:@"config" message:message]; [self.activeKeyboards addObject:partialPath]; [self saveActiveKeyboards]; } else if (checkBox.state == NSOffState) { - os_log_debug([KMLogs uiLog], "disabling active keyboard: %{public}@", kmxFilePath); - NSString *message = [NSString stringWithFormat:@"disabling active keyboard: %@", kmxFilePath]; + os_log_debug([KMLogs uiLog], "disabling active keyboard: %{public}@", kmxFileName); + NSString *message = [NSString stringWithFormat:@"disabling active keyboard: %@", kmxFileName]; [KMSentryHelper addUserBreadCrumb:@"config" message:message]; [self.activeKeyboards removeObject:partialPath]; [self saveActiveKeyboards]; @@ -423,21 +424,23 @@ - (void)removeAction:(id)sender { [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; } - os_log_debug([KMLogs testLog], "entered removeAction for keyboardName: %{public}@", keyboardName); + os_log_debug([KMLogs configLog], "entered removeAction for keyboardName: %{public}@", keyboardName); [self.deleteAlertView beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertFirstButtonReturn) { os_log_debug([KMLogs uiLog], "confirm delete keyboard alert dismissed"); [self deleteFileAtIndex:[NSNumber numberWithInteger:deleteButton.tag]]; - os_log_debug([KMLogs testLog], "removeAction for keyboardName: %{public}@", keyboardName); - [KMSentryHelper addBreadCrumb:@"configure" message:[NSString stringWithFormat:@"remove keyboard: %@", keyboardName]]; + os_log_debug([KMLogs configLog], "removeAction for keyboardName: %{public}@", keyboardName); + [KMSentryHelper addUserBreadCrumb:@"configure" message:[NSString stringWithFormat:@"remove keyboard: %@", keyboardName]]; } self.deleteAlertView = nil; }]; } - (IBAction)downloadAction:(id)sender { + [KMSentryHelper addUserBreadCrumb:@"user" message:@"download keyboard"]; + if (self.AppDelegate.infoWindow_.window != nil) [self.AppDelegate.infoWindow_ close]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index ad0249643cb..66842926b3f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -18,8 +18,17 @@ #import "PrivacyConsent.h" #import "KMLogs.h" #import "KMSentryHelper.h" +//#include "TargetConditionals.h" @import Sentry; +#if TARGET_CPU_ARM64 +NSString *processorType = @"ARM"; +#elif TARGET_CPU_X86_64 +NSString *processorType = @"Intel"; +#elif +NSString *processorType = @"Unknown"; +#endif + NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification"; @implementation NSString (VersionNumbers) @@ -131,7 +140,7 @@ - (void)inputMethodDeactivated:(NSNotification *)notification { } else { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, OSK already hidden"); } - [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]?@"true":@"false"]; + [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]]; if (self.lowLevelEventTap) { os_log_debug([KMLogs lifecycleLog], "***inputMethodDeactivated, disabling event tap"); @@ -218,10 +227,10 @@ - (BOOL)canForceSentryEvents { BOOL canForce = NO; if (self.sentryTestingEnabled) { - NSString * kmxName = [[self.kmx filePath] lastPathComponent]; NSString * applicationId = [KMInputMethodLifecycle.shared clientApplicationId]; - canForce = [[self getKmxFileName] isEqualToString:testKeyboardName] && [applicationId isEqualToString:testClientApplicationId]; - os_log_info([KMLogs testLog], "canForceSentryEvents, self.sentryTestingEnabled: %{public}@ kmxName: %{public}@ applicationId: %{public}@ testingEnabled: %{public}@", self.sentryTestingEnabled?@"YES":@"NO", kmxName, applicationId, canForce?@"YES":@"NO"); + NSString * kmxFileName = [self getKmxFileName]; + canForce = [kmxFileName isEqualToString:testKeyboardName] && [applicationId isEqualToString:testClientApplicationId]; + os_log_info([KMLogs testLog], "canForceSentryEvents, self.sentryTestingEnabled: %{public}@ kmxName: %{public}@ applicationId: %{public}@ testingEnabled: %{public}@", self.sentryTestingEnabled?@"YES":@"NO", kmxFileName, applicationId, canForce?@"YES":@"NO"); } return canForce; @@ -235,15 +244,12 @@ - (void)setPostLaunchKeymanSentryTags { NSString *keyboardFileName = [self.kmx.filePath lastPathComponent]; os_log_info([KMLogs keyboardLog], "initial kmx set to %{public}@", keyboardFileName); - NSString *hasAccessibility = [PrivacyConsent.shared checkAccessibility]?@"true":@"false"; - // assign custom keyboard tag in Sentry for accessibility and initial keyboard [KMSentryHelper addKeyboardTag:keyboardFileName]; - [KMSentryHelper addHasAccessibilityTag:hasAccessibility]; -// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { -// [scope setTagValue:hasAccessibility forKey:@"accessibilityEnabled"]; -// [scope setTagValue:keyboardFileName forKey:@"keyboard"]; -// }]; + [KMSentryHelper addHasAccessibilityTag:[PrivacyConsent.shared checkAccessibility]]; + [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]]; + [KMSentryHelper addArchitectureTag:processorType]; + } #ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE @@ -354,6 +360,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef os_log_debug([KMLogs eventsLog], "*** kKeymanEventKeyCode = 0xFF"); } else { if ([OSKView isOskKeyDownEvent:event]) { + [KMSentryHelper addBreadCrumb:@"event" message:@"processing OSK-generated keydown event"]; NSEventModifierFlags oskEventModifiers = [OSKView extractModifierFlagsFromOskEvent:event]; appDelegate.receivedKeyDownFromOsk = YES; appDelegate.oskEventModifiers = oskEventModifiers; @@ -445,13 +452,11 @@ - (void)loadKeyboardFromKmxFile:(KMXFile *)kmx { os_log_info([KMLogs keyboardLog], "loadKeyboardFromKmxFile, keyboard info: %{public}@", keyboardInfo); [KMSentryHelper addBreadCrumb:@"configure" message:[NSString stringWithFormat:@"loadKeyboardFromKmxFile: %@", [self getKmxFileName]]]; + [KMSentryHelper addKeyboardTag:[self getKmxFileName]]; _modifierMapping = [[KMModifierMapping alloc] init:keyboardInfo]; os_log_info([KMLogs keyboardLog], "modifierMapping bothOptionKeysGenerateRightAlt: %d", self.modifierMapping.bothOptionKeysGenerateRightAlt); - - // assign custom keyboard tag in Sentry to default keyboard - [KMSentryHelper addKeyboardTag:keyboardInfo.keyboardId]; } - (void)setKvk:(KVKFile *)kvk { @@ -659,6 +664,8 @@ - (NSMutableArray *)activeKeyboards { if (!_activeKeyboards) { os_log_debug([KMLogs dataLog], "initializing activeKeyboards"); _activeKeyboards = [[KMSettingsRepository.shared readActiveKeyboards] mutableCopy]; + + [KMSentryHelper addActiveKeyboardCountTag:_activeKeyboards.count]; } return _activeKeyboards; @@ -669,6 +676,7 @@ - (void)saveActiveKeyboards { [KMSettingsRepository.shared writeActiveKeyboards:_activeKeyboards]; [self resetActiveKeyboards]; [self updateKeyboardMenuItems]; + [KMSentryHelper addActiveKeyboardCountTag:self.activeKeyboards.count]; } - (void)clearActiveKeyboards { @@ -993,7 +1001,7 @@ - (void)showOSK { [[self.oskWindow window] makeKeyAndOrderFront:nil]; [[self.oskWindow window] setLevel:NSStatusWindowLevel]; [[self.oskWindow window] setTitle:self.oskWindowTitle]; - [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]?@"true":@"false"]; + [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]]; } - (void)showAboutWindow { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index 340a06a04eb..d900cf073ab 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -45,7 +45,6 @@ - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _queuedText = nil; [KMSentryHelper addClientAppIdTag:clientAppId]; - return self; } @@ -139,9 +138,11 @@ - (BOOL)forceSentryEvent:(NSEvent *)event { NSString *sentryCommand = event.characters; if ([sentryCommand isEqualToString:@"{"]) { os_log_debug([KMLogs testLog], "forceSentryEvent: forcing crash now"); + NSBeep(); [SentrySDK crash]; } else if ([sentryCommand isEqualToString:@"["]) { os_log_debug([KMLogs testLog], "forceSentryEvent: forcing message now"); + NSBeep(); [SentrySDK captureMessage:@"Forced test message"]; } else { os_log_debug([KMLogs testLog], "forceSentryEvent: unrecognized command"); @@ -162,7 +163,8 @@ - (BOOL)forceSentryEvent:(NSEvent *)event { */ - (void)handleBackspace:(NSEvent *)event { os_log_debug([KMLogs eventsLog], "KMInputMethodEventHandler handleBackspace, event = %{public}@", event); - + [KMSentryHelper addBreadCrumb:@"user" message:@"handle backspace for non-compliant app"]; + if (self.generatedBackspaceCount > 0) { self.generatedBackspaceCount--; self.lowLevelBackspaceCount++; @@ -218,6 +220,7 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { [self handleContextChangedByLowLevelEvent]; if (event.type == NSEventTypeKeyDown) { + [KMSentryHelper addBreadCrumb:@"event" message:@"handling keydown event"]; // indicates that our generated backspace event(s) are consumed // and we can insert text that followed the backspace(s) if (event.keyCode == kKeymanEventKeyCode) { @@ -410,6 +413,7 @@ -(void) persistOptions:(NSDictionary*)options{ NSString *value = [options objectForKey:key]; if(key && value) { os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); + [KMSentryHelper addBreadCrumb:@"event" message:@"persist options"]; [[KMSettingsRepository shared] writeOptionForSelectedKeyboard:key withValue:value]; } else { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h index e9d6936c0e6..5699cef1d2b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h @@ -12,10 +12,12 @@ NS_ASSUME_NONNULL_BEGIN @interface KMSentryHelper : NSObject + (void)addApiComplianceTag: (NSString *)value; -+ (void)addOskVisibleTag:(NSString *)value; ++ (void)addArchitectureTag: (NSString *)value; ++ (void)addOskVisibleTag:(BOOL)value; + (void)addClientAppIdTag:(NSString *)value; + (void)addKeyboardTag:(NSString *)value; -+ (void)addHasAccessibilityTag:(NSString *)value; ++ (void)addHasAccessibilityTag:(BOOL)value; ++ (void)addActiveKeyboardCountTag:(NSUInteger)value; + (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m index 5278e36037d..66656b24d5b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m @@ -12,17 +12,23 @@ @implementation KMSentryHelper NSString * const kApiComplianceTagName = @"apiCompliance"; +NSString * const kArchitectureTagName = @"architecture"; NSString * const kOskVisibleTagName = @"oskVisible"; NSString * const kClientAppIdTagName = @"clientAppId"; NSString * const kKeyboardTagName = @"keyboard"; NSString * const kHasAccessibilityTagName = @"accessibilityEnabled"; +NSString * const kActiveKeyboardCountTagName = @"activeKeyboardCount"; + (void)addApiComplianceTag: (NSString *)value { [self addCustomTag:kApiComplianceTagName withValue:value]; } -+ (void)addOskVisibleTag:(NSString *)value { - [self addCustomTag:kOskVisibleTagName withValue:value]; ++ (void)addArchitectureTag: (NSString *)value { + [self addCustomTag:kArchitectureTagName withValue:value]; +} + ++ (void)addOskVisibleTag:(BOOL)value { + [self addCustomTag:kOskVisibleTagName withValue:value?@"true":@"false"]; } + (void)addClientAppIdTag:(NSString *)value { @@ -33,8 +39,12 @@ + (void)addKeyboardTag:(NSString *)value { [self addCustomTag:kKeyboardTagName withValue:value]; } -+ (void)addHasAccessibilityTag:(NSString *)value { - [self addCustomTag:kHasAccessibilityTagName withValue:value]; ++ (void)addHasAccessibilityTag:(BOOL)value { + [self addCustomTag:kHasAccessibilityTagName withValue:value?@"true":@"false"]; +} + ++ (void)addActiveKeyboardCountTag:(NSUInteger)value { + [self addCustomTag:kActiveKeyboardCountTagName withValue:[[NSNumber numberWithUnsignedLong:value] stringValue]]; } /** diff --git a/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m index df0e723534d..d4f457c7544 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/OnScreenKeyboard/OSKWindowController.m @@ -77,7 +77,7 @@ - (void)windowWillClose:(NSNotification *)notification { // whenever the OSK is closing clear all of its modifier keys [self.oskView clearOskModifiers]; - [KMSentryHelper addOskVisibleTag:@"false"]; + [KMSentryHelper addOskVisibleTag:NO]; } - (void)helpAction:(id)sender { From 2b390274c87545b1221a263a9a38bd3772356184 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Mon, 20 Jan 2025 16:17:36 +0700 Subject: [PATCH 4/9] chore(mac): add KMSentryHelper to test project resolves link error when running unit tests --- mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 3436ed498a3..87b005309cb 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; 29DBBF672D35053E00D8E33C /* KMSentryHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 29DBBF662D35053E00D8E33C /* KMSentryHelper.m */; }; 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */ = {isa = PBXBuildFile; fileRef = 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */; }; + 29E67D042D3E4A86003CDE8F /* KMSentryHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 29DBBF662D35053E00D8E33C /* KMSentryHelper.m */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; @@ -1052,6 +1053,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 29E67D042D3E4A86003CDE8F /* KMSentryHelper.m in Sources */, 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */, 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */, 2915DC512BFE35DB0051FC52 /* KMLogs.m in Sources */, From d6ce0af77e5bd07ccae568f3823cf1a473968573 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Wed, 22 Jan 2025 11:28:50 +0700 Subject: [PATCH 5/9] chore(mac): added tag for lifecycleState Fixes: #11623 --- .../Keyman4MacIM/KMInputMethodEventHandler.m | 40 ++++++++++++++++--- .../Keyman4MacIM/KMInputMethodLifecycle.m | 40 +++++++++++++++++-- .../Keyman4MacIM/Logging/KMSentryHelper.h | 2 + .../Keyman4MacIM/Logging/KMSentryHelper.m | 15 ++++++- .../Keyman4MacIM/TextApiCompliance.m | 4 ++ 5 files changed, 89 insertions(+), 12 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index d900cf073ab..fcb381013e7 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -44,6 +44,10 @@ - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _lowLevelBackspaceCount = 0; _queuedText = nil; + _apiCompliance = [[TextApiCompliance alloc]initWithClient:sender applicationId:clientAppId]; + os_log_info([KMLogs lifecycleLog], "KMInputMethodEventHandler initWithClient, clientAppId: %{public}@", clientAppId); + [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"KMInputMethodEventHandler initWithClient, clientAppId '%@'", clientAppId]]; + [KMSentryHelper addClientAppIdTag:clientAppId]; return self; } @@ -184,13 +188,10 @@ - (void)triggerInsertQueuedText:(NSEvent *)event { //MARK: Core-related key processing - (void)checkTextApiCompliance:(id)client { - // If the TextApiCompliance object is nil or the app or the context has changed, - // then create a new object for the current application and context. + // If the TextApiCompliance object is stale, create a new one for the current application and context. // The text api compliance may vary from one text field to another of the same app. - if ((self.apiCompliance == nil) || - (![self.apiCompliance.clientApplicationId isEqualTo:self.clientApplicationId]) || - self.contextChanged) { + if ([self textApiComplianceIsStale]) { self.apiCompliance = [[TextApiCompliance alloc]initWithClient:client applicationId:self.clientApplicationId]; os_log_debug([KMLogs complianceLog], "KMInputMethodHandler initWithClient checkTextApiCompliance: %{public}@", _apiCompliance); } else if (self.apiCompliance.isComplianceUncertain) { @@ -199,6 +200,33 @@ - (void)checkTextApiCompliance:(id)client { } } +- (BOOL)textApiComplianceIsStale { + BOOL stale = false; + NSString *complianceAppId = nil; + TextApiCompliance *currentApiCompliance = self.apiCompliance; + + // test for three scenarios in which the api compliance is stale + if (currentApiCompliance == nil) { // if we have no previous api compliance object + stale = true; + } else { // if we have one but for a different client + complianceAppId = self.apiCompliance.clientApplicationId; + if ([complianceAppId isNotEqualTo:self.clientApplicationId]) { + stale = true; + } else { + stale = false; + } + } + if (self.contextChanged) { // if the context has changed + stale = true; + } + + NSString *message = [NSString stringWithFormat:@"textApiComplianceIsStale = %@ for appId: %@, apiCompliance: %p, complianceAppId: %@", stale?@"true":@"false", self.clientApplicationId, currentApiCompliance, complianceAppId]; + [KMSentryHelper addDebugBreadCrumb:@"compliance" message:message]; + os_log_debug([KMLogs complianceLog], "%{public}@", message); + + return stale; +} + - (void) handleContextChangedByLowLevelEvent { if (self.appDelegate.contextChangedByLowLevelEvent) { if (!self.contextChanged) { @@ -220,7 +248,7 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { [self handleContextChangedByLowLevelEvent]; if (event.type == NSEventTypeKeyDown) { - [KMSentryHelper addBreadCrumb:@"event" message:@"handling keydown event"]; + [KMSentryHelper addDebugBreadCrumb:@"event" message:@"handling keydown event"]; // indicates that our generated backspace event(s) are consumed // and we can insert text that followed the backspace(s) if (event.keyCode == kKeymanEventKeyCode) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m index 543d7fe4d8d..6c98c17b3c3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m @@ -66,6 +66,8 @@ @interface KMInputMethodLifecycle() @implementation KMInputMethodLifecycle +@synthesize lifecycleState = _lifecycleState; + + (KMInputMethodLifecycle *)shared { static KMInputMethodLifecycle *shared = nil; static dispatch_once_t onceToken; @@ -86,11 +88,40 @@ - (instancetype)init { return self; } +- (void)setLifecycleState:(LifecycleState)state { + _lifecycleState = state; + + // whenever the state is changed, update the Sentry tag + [self addLifecycleStateSentryTag]; +} + +- (void)addLifecycleStateSentryTag { + NSString *stateString = @"Unknown"; + switch (self.lifecycleState) { + case Started: + stateString = @"Started"; + break; + case Active: + stateString = @"Active"; + break; + case Inactive: + stateString = @"Inactive"; + break; + } + [KMSentryHelper addLifecycleStateTag:stateString]; + os_log_info([KMLogs lifecycleLog], "setLifecycleState: %{public}@", stateString); +} + + +- (LifecycleState)lifecycleState { + return _lifecycleState; +} + /** * called from Application Delgate during init */ - (void)startLifecycle { - _lifecycleState = Started; + self.lifecycleState = Started; } /** @@ -109,6 +140,7 @@ + (NSString*)getCurrentInputSourceId { + (NSString*)getRunningApplicationId { NSRunningApplication *currentApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; NSString *clientAppId = [currentApp bundleIdentifier]; + os_log_debug([KMLogs lifecycleLog], "getRunningApplicationId, frontmost: %{public}@", clientAppId); return clientAppId; } @@ -217,7 +249,7 @@ - (void)performTransitionAfterDelay:(id)client { */ - (void)activateInputMethod { os_log_debug([KMLogs lifecycleLog], "activateInputMethod"); - _lifecycleState = Active; + self.lifecycleState = Active; [[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodActivatedNotification object:self]; } @@ -226,7 +258,7 @@ - (void)activateInputMethod { */ - (void)deactivateInputMethod { os_log_debug([KMLogs lifecycleLog], "deactivateInputMethod"); - _lifecycleState = Inactive; + self.lifecycleState = Inactive; [[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodDeactivatedNotification object:self]; } @@ -234,7 +266,7 @@ - (void)deactivateInputMethod { * Does not change lifecycleState, just fires notification so that InputController knows to change the event handler */ - (void)changeClient { - os_log_debug([KMLogs lifecycleLog], "changeClient"); + os_log_debug([KMLogs lifecycleLog], "changeClient, posting kInputMethodClientChangeNotification"); [[NSNotificationCenter defaultCenter] postNotificationName:kInputMethodClientChangeNotification object:self]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h index 5699cef1d2b..adbce67a538 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @interface KMSentryHelper : NSObject ++ (void)addLifecycleStateTag: (NSString *)value; + (void)addApiComplianceTag: (NSString *)value; + (void)addArchitectureTag: (NSString *)value; + (void)addOskVisibleTag:(BOOL)value; @@ -19,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)addHasAccessibilityTag:(BOOL)value; + (void)addActiveKeyboardCountTag:(NSUInteger)value; + (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText; ++ (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m index 66656b24d5b..b28e0b5b76d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m @@ -18,6 +18,11 @@ @implementation KMSentryHelper NSString * const kKeyboardTagName = @"keyboard"; NSString * const kHasAccessibilityTagName = @"accessibilityEnabled"; NSString * const kActiveKeyboardCountTagName = @"activeKeyboardCount"; +NSString * const kLifecycleStateTagName = @"lifecycleState"; + ++ (void)addLifecycleStateTag: (NSString *)value { + [self addCustomTag:kLifecycleStateTagName withValue:value]; +} + (void)addApiComplianceTag: (NSString *)value { [self addCustomTag:kApiComplianceTagName withValue:value]; @@ -61,7 +66,14 @@ + (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText { crumb.level = kSentryLevelInfo; crumb.category = category; crumb.message = messageText; - //crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; + [SentrySDK addBreadcrumb:crumb]; +} + ++ (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText { + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelDebug; + crumb.category = category; + crumb.message = messageText; [SentrySDK addBreadcrumb:crumb]; } @@ -71,7 +83,6 @@ + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText { crumb.level = kSentryLevelInfo; crumb.category = category; crumb.message = messageText; - //crumb.message = [NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]; [SentrySDK addBreadcrumb:crumb]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m index 4d77bada0f1..81f50c5edda 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m @@ -63,6 +63,10 @@ -(instancetype)initWithClient:(id) client applicationId:(NSString *)appId { _clientApplicationId = appId; _complianceUncertain = YES; _initialSelection = NSMakeRange(NSNotFound, NSNotFound); + + NSString *message = [NSString stringWithFormat:@"TextApiCompliance initWithClient, client: %p applicationId: %@", client, appId]; + [KMSentryHelper addDebugBreadCrumb:@"compliance" message:message]; + os_log_debug([KMLogs complianceLog], "%{public}@", message); // if we do not have hard-coded noncompliance, then test the app if (![self applyNoncompliantAppLists:appId]) { From 0ec2664126941726d6ddd07c58f8aa427150d4d6 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Wed, 22 Jan 2025 12:52:44 +0700 Subject: [PATCH 6/9] chore(mac): clean up comments --- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 66842926b3f..3c2fd7f7109 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -18,7 +18,6 @@ #import "PrivacyConsent.h" #import "KMLogs.h" #import "KMSentryHelper.h" -//#include "TargetConditionals.h" @import Sentry; #if TARGET_CPU_ARM64 @@ -198,7 +197,6 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self updateKeyboardMenuItems]; [self setPostLaunchKeymanSentryTags]; self.sentryTestingEnabled = [KMSettingsRepository.shared readForceSentryError]; - // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; } - (void)startSentry { @@ -209,7 +207,6 @@ - (void)startSentry { options.dsn = @"https://960f8b8e574c46e3be385d60ce8e1fea@o1005580.ingest.sentry.io/5983522"; options.releaseName = releaseName; options.environment = keymanVersionInfo.sentryEnvironment; - //options.debug = YES; }]; } @@ -244,23 +241,13 @@ - (void)setPostLaunchKeymanSentryTags { NSString *keyboardFileName = [self.kmx.filePath lastPathComponent]; os_log_info([KMLogs keyboardLog], "initial kmx set to %{public}@", keyboardFileName); - // assign custom keyboard tag in Sentry for accessibility and initial keyboard + // assign custom keyboard tags in Sentry [KMSentryHelper addKeyboardTag:keyboardFileName]; [KMSentryHelper addHasAccessibilityTag:[PrivacyConsent.shared checkAccessibility]]; [KMSentryHelper addOskVisibleTag:[self.oskWindow.window isVisible]]; [KMSentryHelper addArchitectureTag:processorType]; - } -#ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE -- (BOOL)alertShowHelp:(NSAlert *)alert { - os_log_error([KMLogs startupLog], "Sentry - KME: Got call to force crash from engine"); - [SentrySDK crash]; - os_log_error([KMLogs startupLog], "Sentry - KME: should not have gotten this far!"); - return NO; -} -#endif - - (void)handleURLEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent { [self processURL:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; From c9c47e8438a99793b8bb1595ebfeed1edad482be Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Thu, 23 Jan 2025 11:42:47 +0700 Subject: [PATCH 7/9] chore(mac): fixed else statement for architecture tag --- mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 3c2fd7f7109..b3020e1a55d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -24,7 +24,7 @@ NSString *processorType = @"ARM"; #elif TARGET_CPU_X86_64 NSString *processorType = @"Intel"; -#elif +#else NSString *processorType = @"Unknown"; #endif From 077229e89c7de60d56385e3ebf47a8f8c2340abc Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Mon, 27 Jan 2025 09:21:26 +0700 Subject: [PATCH 8/9] chore(mac): added comments and renamed function --- .../KMConfigurationWindowController.m | 2 +- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 10 ++++---- .../Keyman4MacIM/KMInputMethodEventHandler.m | 6 ++--- .../Keyman4MacIM/KMInputMethodLifecycle.m | 6 ++--- .../Keyman4MacIM/Logging/KMSentryHelper.h | 4 ++-- .../Keyman4MacIM/Logging/KMSentryHelper.m | 24 +++++++++---------- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 6f63bcfccf3..8a35afd31c9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -472,7 +472,7 @@ - (void)handleRequestToInstallPackage:(KMPackage *) package { - (void)installPackageFile:(NSString *)kmpFile { // kmpFile could be a temp file (in fact, it always is!), so don't display the name. os_log_debug([KMLogs dataLog], "kmpFile - ready to unzip/install Package File: %{public}@", kmpFile); - [KMSentryHelper addBreadCrumb:@"configure" message:@"install package file"]; + [KMSentryHelper addInfoBreadCrumb:@"configure" message:@"install package file"]; BOOL didUnzip = [self.AppDelegate unzipFile:kmpFile]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index b3020e1a55d..cfff0d80cd6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -134,7 +134,7 @@ - (void)initCompletion { - (void)inputMethodDeactivated:(NSNotification *)notification { if ([self.oskWindow.window isVisible]) { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, hiding OSK"); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:@"hiding OSK on input method deactivation"]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:@"hiding OSK on input method deactivation"]; [self.oskWindow.window setIsVisible:NO]; } else { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodDeactivated, OSK already hidden"); @@ -160,7 +160,7 @@ - (void)inputMethodActivated:(NSNotification *)notification { if (_kvk != nil && ([KMInputMethodLifecycle.shared shouldShowOskOnActivate])) { os_log_debug([KMLogs oskLog], "***KMInputMethodAppDelegate inputMethodActivated, showing OSK"); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:@"opening OSK on input method activation"]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:@"opening OSK on input method activation"]; [self showOSK]; } } @@ -347,7 +347,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef os_log_debug([KMLogs eventsLog], "*** kKeymanEventKeyCode = 0xFF"); } else { if ([OSKView isOskKeyDownEvent:event]) { - [KMSentryHelper addBreadCrumb:@"event" message:@"processing OSK-generated keydown event"]; + [KMSentryHelper addInfoBreadCrumb:@"event" message:@"processing OSK-generated keydown event"]; NSEventModifierFlags oskEventModifiers = [OSKView extractModifierFlagsFromOskEvent:event]; appDelegate.receivedKeyDownFromOsk = YES; appDelegate.oskEventModifiers = oskEventModifiers; @@ -438,7 +438,7 @@ - (void)loadKeyboardFromKmxFile:(KMXFile *)kmx { CoreKeyboardInfo *keyboardInfo = [self.kme loadKeyboardFromKmxFile:kmx]; os_log_info([KMLogs keyboardLog], "loadKeyboardFromKmxFile, keyboard info: %{public}@", keyboardInfo); - [KMSentryHelper addBreadCrumb:@"configure" message:[NSString stringWithFormat:@"loadKeyboardFromKmxFile: %@", [self getKmxFileName]]]; + [KMSentryHelper addInfoBreadCrumb:@"configure" message:[NSString stringWithFormat:@"loadKeyboardFromKmxFile: %@", [self getKmxFileName]]]; [KMSentryHelper addKeyboardTag:[self getKmxFileName]]; _modifierMapping = [[KMModifierMapping alloc] init:keyboardInfo]; @@ -864,7 +864,7 @@ - (void) setDefaultSelectedKeyboard { [self setSelectedKeyboard:keyboardName]; [self setContextBuffer:nil]; - [KMSentryHelper addBreadCrumb:@"startup" message:[NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]]; + [KMSentryHelper addInfoBreadCrumb:@"startup" message:[NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]]; } - (void) addKeyboardPlaceholderMenuItem { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index fcb381013e7..d4ab67d10fe 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -46,7 +46,7 @@ - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender { _apiCompliance = [[TextApiCompliance alloc]initWithClient:sender applicationId:clientAppId]; os_log_info([KMLogs lifecycleLog], "KMInputMethodEventHandler initWithClient, clientAppId: %{public}@", clientAppId); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"KMInputMethodEventHandler initWithClient, clientAppId '%@'", clientAppId]]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"KMInputMethodEventHandler initWithClient, clientAppId '%@'", clientAppId]]; [KMSentryHelper addClientAppIdTag:clientAppId]; return self; @@ -167,7 +167,7 @@ - (BOOL)forceSentryEvent:(NSEvent *)event { */ - (void)handleBackspace:(NSEvent *)event { os_log_debug([KMLogs eventsLog], "KMInputMethodEventHandler handleBackspace, event = %{public}@", event); - [KMSentryHelper addBreadCrumb:@"user" message:@"handle backspace for non-compliant app"]; + [KMSentryHelper addInfoBreadCrumb:@"user" message:@"handle backspace for non-compliant app"]; if (self.generatedBackspaceCount > 0) { self.generatedBackspaceCount--; @@ -441,7 +441,7 @@ -(void) persistOptions:(NSDictionary*)options{ NSString *value = [options objectForKey:key]; if(key && value) { os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); - [KMSentryHelper addBreadCrumb:@"event" message:@"persist options"]; + [KMSentryHelper addInfoBreadCrumb:@"event" message:@"persist options"]; [[KMSettingsRepository shared] writeOptionForSelectedKeyboard:key withValue:value]; } else { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m index 6c98c17b3c3..ba53d9ffa65 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodLifecycle.m @@ -199,7 +199,7 @@ - (void)performTransition:(id)client { break; case Activate: os_log_info([KMLogs lifecycleLog], "performTransition: Activate, new application ID: %{public}@", currentClientAppId); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"activated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"activated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; /** * Perform two actions when activating the input method. * Change the client first which prepares the event handler. @@ -210,12 +210,12 @@ - (void)performTransition:(id)client { break; case Deactivate: os_log_info([KMLogs lifecycleLog], "performTransition: Deactivate, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"deactivated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"deactivated input method '%@' for application ID '%@'", currentInputSource, currentClientAppId]]; [self deactivateInputMethod]; break; case ChangeClients: os_log_info([KMLogs lifecycleLog], "performTransition: ChangeClients, new InputSourceId: %{public}@, new application ID: %{public}@", currentInputSource, currentClientAppId); - [KMSentryHelper addBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"change clients for input method '%@' to application ID '%@'", currentInputSource, currentClientAppId]]; + [KMSentryHelper addInfoBreadCrumb:@"lifecycle" message:[NSString stringWithFormat:@"change clients for input method '%@' to application ID '%@'", currentInputSource, currentClientAppId]]; [self changeClient]; break; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h index adbce67a538..8a7d458967c 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h @@ -1,5 +1,5 @@ /* - * Keyman is copyright (C) SIL International. MIT License. + * Keyman is copyright (C) SIL Global. MIT License. * * Created by Shawn Schantz on 2025-01-13. * @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)addKeyboardTag:(NSString *)value; + (void)addHasAccessibilityTag:(BOOL)value; + (void)addActiveKeyboardCountTag:(NSUInteger)value; -+ (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText; ++ (void)addInfoBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m index b28e0b5b76d..7c37be9ad99 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.m @@ -1,5 +1,5 @@ /* - * Keyman is copyright (C) SIL International. MIT License. + * Keyman is copyright (C) SIL Global. MIT License. * * Created by Shawn Schantz on 2025-01-13. * @@ -53,7 +53,7 @@ + (void)addActiveKeyboardCountTag:(NSUInteger)value { } /** - *assign custom keyboard tag in Sentry to initial keyboard + *assign custom keyboard tag in Sentry for the given name and value */ + (void)addCustomTag:(NSString *)tagName withValue:(NSString *)value { [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { @@ -61,7 +61,10 @@ + (void)addCustomTag:(NSString *)tagName withValue:(NSString *)value { }]; } -+ (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText { +/** + *add a Sentry breadcrumb message of level Info with the specified category + */ ++ (void)addInfoBreadCrumb:(NSString *)category message:(NSString *)messageText { SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; crumb.level = kSentryLevelInfo; crumb.category = category; @@ -69,6 +72,9 @@ + (void)addBreadCrumb:(NSString *)category message:(NSString *)messageText { [SentrySDK addBreadcrumb:crumb]; } +/** + *add a Sentry breadcrumb message of level debug with the specified category + */ + (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText { SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; crumb.level = kSentryLevelDebug; @@ -77,6 +83,9 @@ + (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText [SentrySDK addBreadcrumb:crumb]; } +/** + *add a Sentry breadcrumb message of type 'user' and level debug with the specified category + */ + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText { SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; crumb.type = @"user"; @@ -86,13 +95,4 @@ + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText { [SentrySDK addBreadcrumb:crumb]; } -+ (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map { - SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; - crumb.level = kSentryLevelInfo; - crumb.category = category; - crumb.message = messageText; - crumb.data = map; - [SentrySDK addBreadcrumb:crumb]; -} - @end From e5820f532237812b63c32e7a103a57020dd3d920 Mon Sep 17 00:00:00 2001 From: sgschantz <shawn_schantz@sil.org> Date: Mon, 27 Jan 2025 12:52:41 +0700 Subject: [PATCH 9/9] chore(mac): remove extraneous code --- .../KMConfiguration/KMConfigurationWindowController.m | 4 +--- .../KMDownloadKeyboard/KMDownloadKBWindowController.m | 1 - mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 8a35afd31c9..2b06db4cade 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -418,12 +418,10 @@ - (void)removeAction:(id)sender { if ([info objectForKey:@"HeaderTitle"] != nil) { keyboardName = [info objectForKey:@"HeaderTitle"]; - [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; } else { keyboardName = [info objectForKey:kKMKeyboardNameKey]; - [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; } - + [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, keyboardName]]; os_log_debug([KMLogs configLog], "entered removeAction for keyboardName: %{public}@", keyboardName); [self.deleteAlertView beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m index 2fec9f8b320..4b5ce44dea8 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.m @@ -9,7 +9,6 @@ #import "KMDownloadKBWindowController.h" #import "KMInputMethodAppDelegate.h" #import "KMLogs.h" -#import "KMSentryHelper.h" @interface KMDownloadKBWindowController () @property (nonatomic, weak) IBOutlet WebView *webView; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h index 8a7d458967c..75a5c272825 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMSentryHelper.h @@ -22,7 +22,6 @@ NS_ASSUME_NONNULL_BEGIN + (void)addInfoBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addDebugBreadCrumb:(NSString *)category message:(NSString *)messageText; + (void)addUserBreadCrumb:(NSString *)category message:(NSString *)messageText; -+ (void)addDetailedBreadCrumb:(NSString *)category message:(NSString *)messageText data:( NSDictionary *) map; @end