Fix IME activation issues on macOS 12
We now let the Installer to call the TextInputSources API. Since macOS 12, users are prompted to allow enabling of third-party IMEs in Preferences.app the momemnt TISRegisterInputSource or TISEnableInputSource is called. By moving the activation to the Installer, a user will clearly see that it's the Installer that wants to enable the IME. In addition, we had to make necessary changes so that on macOS 12 and later, the Installer always enable the default input source. This is due to the observation that the kTISPropertyInputSourceIsEnabled becomes unreliable on macOS 12--it may be true even if the user has removed the input mode from their active input mode list in Preferences.app.
This commit is contained in:
parent
b85029dec1
commit
c1bea8c382
|
@ -33,6 +33,7 @@
|
||||||
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
||||||
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
|
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
|
||||||
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
||||||
|
6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; };
|
||||||
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
||||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; };
|
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; };
|
||||||
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
|
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
|
||||||
|
@ -660,6 +661,7 @@
|
||||||
files = (
|
files = (
|
||||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
|
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
|
||||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
|
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
|
||||||
|
6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */,
|
||||||
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -1038,6 +1040,7 @@
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
HEADER_SEARCH_PATHS = ../;
|
||||||
INFOPLIST_FILE = "Source/Installer/Installer-Info.plist";
|
INFOPLIST_FILE = "Source/Installer/Installer-Info.plist";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
@ -1070,6 +1073,7 @@
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
HEADER_SEARCH_PATHS = ../;
|
||||||
INFOPLIST_FILE = "Source/Installer/Installer-Info.plist";
|
INFOPLIST_FILE = "Source/Installer/Installer-Info.plist";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.McBopomofo.${PRODUCT_NAME:rfc1034identifier}";
|
PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.McBopomofo.${PRODUCT_NAME:rfc1034identifier}";
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import <sys/mount.h>
|
#import <sys/mount.h>
|
||||||
|
#import "OVInputSourceHelper.h"
|
||||||
|
|
||||||
static NSString *const kTargetBin = @"McBopomofo";
|
static NSString *const kTargetBin = @"McBopomofo";
|
||||||
static NSString *const kTargetType = @"app";
|
static NSString *const kTargetType = @"app";
|
||||||
|
@ -55,7 +56,6 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
||||||
@synthesize progressSheet = _progressSheet;
|
@synthesize progressSheet = _progressSheet;
|
||||||
@synthesize progressIndicator = _progressIndicator;
|
@synthesize progressIndicator = _progressIndicator;
|
||||||
|
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
_installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
_installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
||||||
|
@ -130,9 +130,9 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
||||||
// Schedule the install action in runloop so that the sheet gets a change to dismiss itself.
|
// Schedule the install action in runloop so that the sheet gets a change to dismiss itself.
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if (returnCode == NSModalResponseContinue) {
|
if (returnCode == NSModalResponseContinue) {
|
||||||
[self installInputMethodWithWarning:NO];
|
[self installInputMethodWithPreviousExists:YES previousVersionNotFullyDeactivatedWarning:NO];
|
||||||
} else {
|
} else {
|
||||||
[self installInputMethodWithWarning:YES];
|
[self installInputMethodWithPreviousExists:YES previousVersionNotFullyDeactivatedWarning:YES];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}];
|
}];
|
||||||
|
@ -143,7 +143,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[self installInputMethodWithWarning:NO];
|
[self installInputMethodWithPreviousExists:NO previousVersionNotFullyDeactivatedWarning:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)timerTick:(NSTimer *)timer
|
- (void)timerTick:(NSTimer *)timer
|
||||||
|
@ -162,7 +162,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)installInputMethodWithWarning:(BOOL)warning
|
- (void)installInputMethodWithPreviousExists:(BOOL)previousVersionExists previousVersionNotFullyDeactivatedWarning:(BOOL)warning
|
||||||
{
|
{
|
||||||
// If the unzipped archive does not exist, this must be a dev-mode installer.
|
// If the unzipped archive does not exist, this must be a dev-mode installer.
|
||||||
NSString *targetBundle = [_archiveUtil unzipNotarizedArchive];
|
NSString *targetBundle = [_archiveUtil unzipNotarizedArchive];
|
||||||
|
@ -177,23 +177,80 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
||||||
[NSApp terminate:self];
|
[NSApp terminate:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray *installArgs = [NSArray arrayWithObjects:@"install", nil];
|
NSBundle *imeBundle = [NSBundle bundleWithPath:[kTargetPartialPath stringByExpandingTildeInPath]];
|
||||||
NSTask *installTask = [NSTask launchedTaskWithLaunchPath:[kTargetFullBinPartialPath stringByExpandingTildeInPath] arguments:installArgs];
|
NSCAssert(imeBundle != nil, @"Target bundle must exists");
|
||||||
[installTask waitUntilExit];
|
NSURL *imeBundleURL = imeBundle.bundleURL;
|
||||||
if ([installTask terminationStatus] != 0) {
|
NSString *imeIdentifier = imeBundle.bundleIdentifier;
|
||||||
RunAlertPanel(NSLocalizedString(@"Install Failed", nil), NSLocalizedString(@"Cannot activate the input method.", nil), NSLocalizedString(@"Cancel", nil));
|
|
||||||
[NSApp terminate:self];
|
TISInputSourceRef inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier];
|
||||||
|
|
||||||
|
// if this IME name is not found in the list of available IMEs
|
||||||
|
if (!inputSource) {
|
||||||
|
NSLog(@"Registering input source %@ at %@.", imeIdentifier, imeBundleURL.absoluteString);
|
||||||
|
// then register
|
||||||
|
BOOL status = [OVInputSourceHelper registerInputSource:imeBundleURL];
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
// TODO: Localize.
|
||||||
|
NSString *message = [NSString stringWithFormat:@"Fatal error: Cannot register input source %@ at %@.", imeIdentifier, imeBundleURL.absoluteString];
|
||||||
|
RunAlertPanel(@"Fatal Error", message, @"Abort");
|
||||||
|
[self endAppWithDelay];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier];
|
||||||
|
// if it still doesn't register successfully, bail.
|
||||||
|
if (!inputSource) {
|
||||||
|
// TODO: Localize.
|
||||||
|
NSString *message = [NSString stringWithFormat:@"Fatal error: Cannot find input source %@ after registration.", imeIdentifier];
|
||||||
|
RunAlertPanel(@"Fatal Error", message, @"Abort");
|
||||||
|
[self endAppWithDelay];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL isMacOS12OrAbove = NO;
|
||||||
|
if (@available(macOS 12.0, *)) {
|
||||||
|
NSLog(@"macOS 12 or later detected.");
|
||||||
|
isMacOS12OrAbove = YES;
|
||||||
|
} else {
|
||||||
|
NSLog(@"Installer runs with the pre-macOS 12 flow.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the IME is not enabled, enable it. Also, unconditionally enable it on macOS 12.0+,
|
||||||
|
// as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not*
|
||||||
|
// enabled in the user's current set of IMEs (which means the IME does not show up in
|
||||||
|
// the user's input menu).
|
||||||
|
BOOL mainInputSourceEnabled = [OVInputSourceHelper inputSourceEnabled:inputSource];
|
||||||
|
if (!mainInputSourceEnabled || isMacOS12OrAbove) {
|
||||||
|
|
||||||
|
mainInputSourceEnabled = [OVInputSourceHelper enableInputSource:inputSource];
|
||||||
|
if (mainInputSourceEnabled) {
|
||||||
|
NSLog(@"Input method enabled: %@", imeIdentifier);
|
||||||
|
} else {
|
||||||
|
NSLog(@"Failed to enable input method: %@", imeIdentifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warning) {
|
if (warning) {
|
||||||
RunAlertPanel(NSLocalizedString(@"Attention", nil), NSLocalizedString(@"McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional.", nil), NSLocalizedString(@"OK", nil));
|
RunAlertPanel(NSLocalizedString(@"Attention", nil), NSLocalizedString(@"McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional.", nil), NSLocalizedString(@"OK", nil));
|
||||||
} else {
|
} else {
|
||||||
RunAlertPanel(NSLocalizedString(@"Installation Successful", nil), NSLocalizedString(@"McBopomofo is ready to use.", nil), NSLocalizedString(@"OK", nil));
|
// Only prompt a warning if pre-macOS 12. The flag is not indicative of anything meaningful due to the need of user intervention in Prefernces.app on macOS 12.
|
||||||
|
if (!mainInputSourceEnabled && !isMacOS12OrAbove) {
|
||||||
|
// TODO: Localize
|
||||||
|
RunAlertPanel(@"Warning", @"Input method may not be fully enabled. Please check Preferences.app.", @"Continue");
|
||||||
|
} else {
|
||||||
|
RunAlertPanel(NSLocalizedString(@"Installation Successful", nil), NSLocalizedString(@"McBopomofo is ready to use.", nil), NSLocalizedString(@"OK", nil));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[NSApplication sharedApplication] performSelector:@selector(terminate:) withObject:self afterDelay:0.1];
|
[self endAppWithDelay];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)endAppWithDelay
|
||||||
|
{
|
||||||
|
[[NSApplication sharedApplication] performSelector:@selector(terminate:) withObject:self afterDelay:0.1];
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)cancelAction:(id)sender
|
- (IBAction)cancelAction:(id)sender
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource;
|
+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource;
|
||||||
+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource;
|
+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource;
|
||||||
+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID;
|
+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID;
|
||||||
|
+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID;
|
||||||
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource;
|
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource;
|
||||||
|
|
||||||
// register (i.e. make available to Input Source tab in Language & Text Preferences)
|
// register (i.e. make available to Input Source tab in Language & Text Preferences)
|
||||||
|
|
|
@ -90,6 +90,24 @@
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID
|
||||||
|
{
|
||||||
|
for (id source in [self allInstalledInputSources]) {
|
||||||
|
TISInputSourceRef inputSource = (__bridge TISInputSourceRef)source;
|
||||||
|
NSString *inputSoureBundleID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyBundleID);
|
||||||
|
NSString *inputSourceModeID = (NSString *)CFBridgingRelease(TISGetInputSourceProperty(inputSource, kTISPropertyInputModeID));
|
||||||
|
|
||||||
|
if ([modeID isEqual:inputSourceModeID] && [bundleID isEqual:inputSoureBundleID]) {
|
||||||
|
BOOL enabled = [self enableInputSource:inputSource];
|
||||||
|
NSLog(@"Attempt to enable input source of mode: %@, bundle ID: %@, result: %d", modeID, bundleID, enabled);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"Failed to find any matching input source of mode: %@, bundle ID: %@", modeID, bundleID);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource
|
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource
|
||||||
{
|
{
|
||||||
OSStatus status = TISDisableInputSource(inInputSource);
|
OSStatus status = TISDisableInputSource(inInputSource);
|
||||||
|
|
Loading…
Reference in New Issue