From e6797f5a58f2104fbc0a890935a6506c3f05f35a Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Thu, 5 Mar 2026 17:59:43 -0500 Subject: [PATCH] feat(mac): add group ID for Input Method test to see whether the Input Method can access the group container without any warnings from the system. --- mac/Keyman4MacIM/Keyman.entitlements | 4 ++ .../Keyman4MacIM.xcodeproj/project.pbxproj | 4 ++ .../Keyman4MacIM/KMDataRepository.h | 4 ++ .../Keyman4MacIM/KMDataRepository.m | 61 +++++++++++++++++-- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 7 ++- .../KeymanSettings/KeymanSettings.swift | 4 +- .../Persistence/PackageRepository.swift | 7 ++- 7 files changed, 81 insertions(+), 10 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman.entitlements b/mac/Keyman4MacIM/Keyman.entitlements index 49ad0bb090..f90d261480 100644 --- a/mac/Keyman4MacIM/Keyman.entitlements +++ b/mac/Keyman4MacIM/Keyman.entitlements @@ -2,6 +2,10 @@ + com.apple.security.application-groups + + 3YE4W86L3G.com.keyman + com.apple.security.automation.apple-events diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 89bbfb022e..908ad6b5dc 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -1312,6 +1312,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + REGISTER_APP_GROUPS = NO; SDKROOT = macosx; }; name = Debug; @@ -1359,6 +1360,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; + REGISTER_APP_GROUPS = NO; SDKROOT = macosx; }; name = Release; @@ -1404,6 +1406,7 @@ PRODUCT_VERSION = 0.0.1; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + REGISTER_APP_GROUPS = NO; STRIP_INSTALLED_PRODUCT = YES; }; name = Debug; @@ -1447,6 +1450,7 @@ PRODUCT_VERSION = 0.0.1; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + REGISTER_APP_GROUPS = NO; STRIP_INSTALLED_PRODUCT = YES; }; name = Release; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 35d3f427e6..40999e896d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -16,9 +16,13 @@ NS_ASSUME_NONNULL_BEGIN // keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' @property (readonly) NSURL *keymanKeyboardsDirectory; +@property (readonly) NSURL *keyman19ContainerDirectory; +@property (readonly) NSURL *keyman19KeyboardsDirectory; + + (KMDataRepository *)shared; - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; +- (void)createKeyman19SharedDirectoriesIfNecessary; - (BOOL)migrateData; - (NSString*)buildFullPath:(NSString *)fromPartialPath; - (NSString*)trimToPartialPath:(NSString *)fromFullPath; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 4722a86fec..7e443bfc04 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -19,13 +19,15 @@ @implementation KMDataRepository /** - * Two directory trees are represented by the following properties, one in active use - * and one that is obsolete. - * The actively used directories, begin with the parent + * Three directory trees are represented by the following properties, one in active use + * and two that are obsolete. + * The actively used directories, introduced in Keyman 19, are shared via the app group 3YE4W86L3G.com.keyman: + * 'Group Containers/3YE4W86L3G.com.keyman/Library/Application Support/Keyman-Keyboards/ + * The obsolete directories from Keyman 18 are: * applicationSupportSubDirectory: '~/Library/Application Support' * keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' * keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' - * The obsolete directories, begin with the parent + * The obsolete directories from Keyman 17 and earlier are: * documentsSubDirectory: '~/Documents' * obsoleteKeymanKeyboardsDirectory: '~/Documents/Keyman-Keyboards' */ @@ -35,6 +37,9 @@ @synthesize documentsSubDirectory = _documentsSubDirectory; @synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; +@synthesize keyman19ContainerDirectory = _keyman19ContainerDirectory; +@synthesize keyman19KeyboardsDirectory = _keyman19KeyboardsDirectory; + NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; /** * The name of the subdirectory within '~/Library/Application Support'. @@ -44,6 +49,11 @@ NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; +//NSString *const kKeymanGroupId = @"3YE4W86L3G.com.keyman"; +NSString *const kKeymanGroupId = @"3YE4W86L3G.com.keyman"; + +NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/Keyman-Keyboards"; + + (KMDataRepository *)shared { static KMDataRepository *shared = nil; static dispatch_once_t onceToken; @@ -103,8 +113,49 @@ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; return _keymanKeyboardsDirectory; } +- (NSURL *)keyman19ContainerDirectory { + if (_keyman19ContainerDirectory == nil) { + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *containerUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier: kKeymanGroupId]; + + _keyman19ContainerDirectory = containerUrl; + } + return _keyman19ContainerDirectory; +} + +- (NSURL *)keyman19KeyboardsDirectory { + if (_keymanKeyboardsDirectory == nil) { + NSURL *keyboardsUrl = [self.keyman19ContainerDirectory URLByAppendingPathComponent:kContainerKeyboardsPartialPath isDirectory: TRUE]; + _keyman19KeyboardsDirectory = keyboardsUrl; + } + return _keyman19KeyboardsDirectory; +} + /** - * Creates Keyman data directory if it do not exist yet. This is the main data subdirectory: keyman.inputmethod.Keyman + * Creates Keyman 19 data directory if it does not exist yet. This is under 'Group Containers'. + */ +- (void)createKeyman19SharedDirectoriesIfNecessary { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:self.keyman19KeyboardsDirectory.path isDirectory:&isDir]; + + if (!exists) { + NSError *createError = nil; + os_log_info([KMLogs dataLog], "createKeyman19SharedDirectoriesIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keyman19KeyboardsDirectory.path); + [fileManager createDirectoryAtPath:self.keyman19KeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; + if (createError) { + os_log_error([KMLogs dataLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "created Keyman data directory: '%{public}@'", self.keyman19KeyboardsDirectory.path); + } + } else { + os_log_info([KMLogs dataLog], "Keyman data directory already exists: '%{public}@'", self.keyman19KeyboardsDirectory.path); + } +} + +/** + * Creates Keyman data directory if it does not exist yet. This is the main data subdirectory: keyman.inputmethod.Keyman */ - (void)createDataDirectoryIfNecessary { NSFileManager *fileManager = [NSFileManager defaultManager]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index afd1ce9f73..b5b0b5d3e4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -754,7 +754,12 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef * namely, the keyboard data on disk and the settings in UserDefaults */ - (void)prepareStorage { - [KMDataRepository.shared createDataDirectoryIfNecessary]; + os_log_debug([KMLogs dataLog], "*** prepareStorage ***"); + + [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; +// [KMDataRepository.shared createDataDirectoryIfNecessary]; + + // TODO: MAC_CONFIG expand data migration for Keyman 19 if ([KMSettingsRepository.shared dataMigrationNeeded]) { [KMDataRepository.shared migrateData]; diff --git a/mac/KeymanSettings/Sources/KeymanSettings/KeymanSettings.swift b/mac/KeymanSettings/Sources/KeymanSettings/KeymanSettings.swift index 7986989b2f..33ece4eec6 100644 --- a/mac/KeymanSettings/Sources/KeymanSettings/KeymanSettings.swift +++ b/mac/KeymanSettings/Sources/KeymanSettings/KeymanSettings.swift @@ -23,7 +23,7 @@ public class KeymanSettings : ObservableObject { // } // load keyboards from disk - if (self.dataRepository.keyman19DataDirectoryExists()) { + if (self.dataRepository.keyman19SharedDataDirectoryExists()) { let packageSourceArray = self.dataRepository.readKeymanPackagesForKeyman19() // create a KeymanPackage object for each PackageSource object and insert it in the array @@ -32,7 +32,7 @@ public class KeymanSettings : ObservableObject { self.keyboardPackages.append(package) } } else { - self.dataRepository.createKeyman19DataDirectories() + self.dataRepository.createKeyman19SharedDataDirectories() } } } diff --git a/mac/KeymanSettings/Sources/Persistence/PackageRepository.swift b/mac/KeymanSettings/Sources/Persistence/PackageRepository.swift index d4b1187312..3a8fc0fe65 100644 --- a/mac/KeymanSettings/Sources/Persistence/PackageRepository.swift +++ b/mac/KeymanSettings/Sources/Persistence/PackageRepository.swift @@ -21,7 +21,7 @@ public class PackageRepository { /** * Creates the directory tree where keyboards are stored under the standard 'Group Containers' directory */ - func createKeyman19DataDirectories() { + func createKeyman19SharedDataDirectories() { if let keyboardDirectory = pathUtil.keyman19KeyboardsDirectory { do { @@ -63,7 +63,10 @@ public class PackageRepository { } } - public func keyman19DataDirectoryExists() -> Bool { + /** + * Check to see whether the shared Keyman data directory exists under 'Library/Group Containers/' + */ + public func keyman19SharedDataDirectoryExists() -> Bool { guard let keyboardsUrl = self.pathUtil.keyman19KeyboardsDirectory else { return false }