From 85adbea99ef8aced9ccc87bedfe25a428cb9559b Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Thu, 6 Aug 2026 11:24:09 -0400 Subject: [PATCH] feat(mac): check settings state and version --- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 29 ++++-- .../Keyman4MacIM/KMSettingsRepository.h | 15 ++- .../Keyman4MacIM/KMSettingsRepository.m | 95 ++++++++++--------- mac/Keyman4MacIM/Keyman4MacIM/main.m | 23 ++++- 4 files changed, 102 insertions(+), 60 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index d6f2014bb6..96564e9649 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -788,18 +788,27 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef * namely, the keyboard data on disk and the settings in UserDefaults */ - (void)prepareStorage { - os_log_debug([KMLogs dataLog], "*** prepareStorage ***"); + SettingsState state = [KMSettingsRepository.shared determineSettingsState]; - // if necessary, migrate settings and keyboard data for compatibility with Keyman 19 - if ([KMSettingsRepository.shared keyman19SettingsMigrationNeeded]) { - [KMDataRepository.shared migrateDataForKeyman19]; - [KMSettingsRepository.shared migrateSettingsForKeyman19]; + switch (state) { + case KeymanSettingsVersion17: + os_log_info([KMLogs dataLog], "prepareStorage, migration needed for Keyman 17 to current"); + break; + case KeymanSettingsVersion18: + os_log_info([KMLogs dataLog], "prepareStorage, migration needed for Keyman 18 to current"); + [KMDataRepository.shared migrateDataForKeyman19]; + [KMSettingsRepository.shared migrateSettingsForKeyman19]; + break; + case KeymanSettingsNotFound: + os_log_info([KMLogs dataLog], "prepareStorage, settings not found, create them"); + [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; + [KMSettingsRepository.shared createKeyman19SharedSettingsIfNecessary]; + // MAC-CONFIG-TODO: are settings created for fresh install? + break; + case KeymanSettingsVersionCurrent: + os_log_info([KMLogs dataLog], "prepareStorage: settings are current, no migration needed"); + break; } - - // if this is a fresh install, then create the shared directories - [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; - - // MAC-CONFIG-TODO: are settings created for fresh install? } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index 89c2af76d0..c41ecfb7c5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -12,13 +12,22 @@ NS_ASSUME_NONNULL_BEGIN +// for classifying the state of the settings (UserDefaults) and data +// used for determining whether migration is needed of the UserDefaults and Keyman packages +// to a new format and/or location +typedef NS_ENUM(NSInteger, SettingsState) { + KeymanSettingsNotFound = 0, + KeymanSettingsVersion17 = 17, + KeymanSettingsVersion18 = 18, + KeymanSettingsVersionCurrent = 19 +}; + @interface KMSettingsRepository : NSObject + (KMSettingsRepository *)shared; - (instancetype)init; -- (BOOL)keyman18DataMigrationNeeded; -- (BOOL)keyman19SettingsMigrationNeeded; +- (SettingsState)determineSettingsState; +- (void)createKeyman19SharedSettingsIfNecessary; - (void)migrateSettingsForKeyman18; -- (void)setDataModelVersionIfNecessary; - (void)migrateSettingsForKeyman19; - (BOOL)migrateInputMethodSettingsToAppGroup; - (NSString *)readSelectedKeyboard; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 042e808282..a75e167932 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -89,53 +89,53 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInGroupContain return self; } -// MAC-CONFIG-TODO: add support for migration from Keyman 18 and earlier to Keyman 19 - /** - * Determines whether the keyboard data needs to be moved from pre-Keyman-18 location to the Keyman 18 location - * This is true if - * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and - * 2) the value for kVersionStoreDataInLibraryDirectory is < 1, + * Determines the current state of the Keyman settings (UserDefaults), indicating + * whether migration is needed to a new format and/or location or whether this is + * a first-time install and settings must be created. */ -- (BOOL)keyman18DataMigrationNeeded { - BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); +- (SettingsState)determineSettingsState { + SettingsState state = KeymanSettingsVersionCurrent; - BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; - os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); + // settings were moved to share app group beginning in Keyman 19 + if ([self settingsExistForAppGroup]) { + os_log([KMLogs dataLog], "keyman shared settings exist, version is current"); + SettingsState state = KeymanSettingsVersionCurrent; + } else if ([self settingsExistForInputMethod]) { + // In Keyman 18, KMDataModelVersion was added to settings and set to value of 1 + if ([self version18SettingsExistForInputMethod]) { + os_log([KMLogs dataLog], "keyman app (unshared) settings version indicates Keyman 18, packages stored in ~/Library"); + state = KeymanSettingsVersion18; + } else { + // no KMDataModelVersion key is found in the app UserDefaults + os_log([KMLogs dataLog], "lack of keyman settings version indicates Keyman 17 or earlier, packages stored in ~/Documents"); + state = KeymanSettingsVersion17; + } + } else { + // settings do not exist, must be a new install + os_log([KMLogs dataLog], "keyman settings do not exist, must be created for new install"); + state = KeymanSettingsNotFound; + } - BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; - os_log([KMLogs dataLog], "keyman18DataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); - - return migrationNeeded; + return state; } -/** - * Determines whether the keyboard data needs to be moved from the Keyman 18 location to the Keyman 19 location - * This is true if the UserDefaults exist in the old location for the input method - */ -- (BOOL)keyman19SettingsMigrationNeeded { - BOOL keymanSettingsExistForInputMethod = [self inputMethodUserDefaultsExist]; - os_log([KMLogs dataLog], "keyman input method settings exist (for 18 and earlier): %{public}@", keymanSettingsExistForInputMethod ? @"YES" : @"NO" ); - - BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; - os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); - - BOOL migrationNeeded = keymanSettingsExistForInputMethod; - os_log([KMLogs dataLog], "keyman19SettingsMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); - - return migrationNeeded; -} +// MAC-CONFIG-TODO: add support for migration from Keyman 17 and earlier to Keyman 19 - (void)migrateSettingsForKeyman19 { [self migrateInputMethodSettingsToAppGroup]; - // set kDataModelVersion to indicate that we are using the group container - [self.groupDefaults setInteger:kVersionStoreDataInGroupContainer forKey:kDataModelVersion]; - + // set kDataModelVersion for the current format + [self.setDataModelVersionIfNecessary]; + [self removeMigratedInputMethodSettings]; } +- (void)createKeyman19SharedSettingsIfNecessary { + // set kDataModelVersion for the current format + [self.setDataModelVersionIfNecessary]; +} + /** * Move userdefaults from app to app group * Read the settings in the input method's user defaults @@ -194,28 +194,37 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInGroupContain - (void)setDataModelVersionIfNecessary { if (![self dataModelWithKeyboardsInLibrary]) { - [self.groupDefaults setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; + [self.groupDefaults setInteger:kVersionStoreDataInGroupContainer forKey:kDataModelVersion]; } } -// MAC-CONFIG-TODO: remove if obsolete /** - * If the selectedKeyboard has not been set, then the settings have not been saved in the UserDefaults. + * The dataModelVersion field will always exists for Keyman 18 and later, and starting + * with Keyman 19, it will be located in the UserDefaults for the app group. * If this method is called after applicationDidFinishLaunching, then it will always return true. * If called from awakeFromNib, then it will return false when running for the first time. */ -- (BOOL)settingsExist +- (BOOL)settingsExistForAppGroup +{ + return ([self.groupDefaults objectForKey:kDataModelVersion] != nil); +} + +/** + * If the selectedKeyboard has not been set, then the settings have not been saved in the UserDefaults. + * For old versions of Keyman, 17 and earlier, there was no`KMDataModelVersion` to check. + */ +- (BOOL)settingsExistForInputMethod { return ([self.appDefaults objectForKey:kSelectedKeyboardKey] != nil); } /** - * Returns true for Keyman 18 and earlier when settings were stored exclusively for the input method rather than for the app group. - * If the selectedKeyboard has not been set, then the settings are not saved in the input method's UserDefaults. + * The dataModelVersion field will always exists for Keyman 18 and later, but it will be located + * in the app UserDefaults -- for the input method only -- instead of the app group UserDefaults */ -- (BOOL)inputMethodUserDefaultsExist +- (BOOL)version18SettingsExistForInputMethod { - return ([self.appDefaults objectForKey:kSelectedKeyboardKey] != nil); + return ([self.appDefaults objectForKey:kDataModelVersion] != nil); } - (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value { @@ -263,7 +272,7 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInGroupContain // [NSUserDefaults integerForKey] returns zero if the key does not exist NSInteger dataModelVersion = [self.groupDefaults integerForKey:kDataModelVersion]; - return dataModelVersion >= kVersionStoreDataInLibraryDirectory; + return dataModelVersion == kVersionStoreDataInLibraryDirectory; } - (NSString *)readSelectedKeyboard { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/main.m b/mac/Keyman4MacIM/Keyman4MacIM/main.m index 9d783ab70e..faf1dbae02 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/main.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/main.m @@ -43,11 +43,26 @@ void runAsInputMethod(void) { */ int doMigration(void) { os_log_info([KMLogs startupLog], "doMigration executed"); - // if necessary, migrate settings and keyboard data for compatibility with Keyman 19 - if ([KMSettingsRepository.shared keyman19SettingsMigrationNeeded]) { - [KMDataRepository.shared migrateDataForKeyman19]; - [KMSettingsRepository.shared migrateSettingsForKeyman19]; + + SettingsState state = [KMSettingsRepository.shared determineSettingsState]; + + switch (state) { + case KeymanSettingsVersion17: + os_log_info([KMLogs startupLog], "doMigration executed for Keyman 17 to current"); + break; + case KeymanSettingsVersion18: + os_log_info([KMLogs startupLog], "doMigration executed for Keyman 18 to current"); + [KMDataRepository.shared migrateDataForKeyman19]; + [KMSettingsRepository.shared migrateSettingsForKeyman19]; + break; + case KeymanSettingsNotFound: + os_log_info([KMLogs startupLog], "doMigration: no migration needed, settings not found"); + break; + case KeymanSettingsVersionCurrent: + os_log_info([KMLogs startupLog], "doMigration: no migration needed, settings are current"); + break; } + return 0; }