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.
This commit is contained in:
Shawn Schantz 2026-03-05 17:59:43 -05:00
parent 72e35c2c9e
commit e6797f5a58
7 changed files with 81 additions and 10 deletions

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>3YE4W86L3G.com.keyman</string>
</array>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>

View file

@ -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;

View file

@ -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;

View file

@ -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];

View file

@ -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];

View file

@ -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()
}
}
}

View file

@ -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
}