Merge pull request #3949 from keymanapp/feat/mac/1953-user-controllable-legacy-app-list

feat(mac): user-controllable legacy app list
This commit is contained in:
Marc Durdin 2020-11-25 19:46:35 +11:00 • committed by GitHub
commit 0c67dbf5dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 27 deletions

View file

@ -125,6 +125,8 @@ typedef struct {
- (NSString *)oskWindowTitle;
- (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent;
- (KeymanVersionInfo)versionInfo;
- (NSString *)keymanDataPath;
- (NSArray *)legacyAppsUserDefaults;
@end
#endif /* KMInputMethodAppDelegate_h */

View file

@ -21,11 +21,14 @@
#import "ZipArchive.h"
@import Sentry;
/** NSUserDefaults keys */
NSString *const kKMSelectedKeyboardKey = @"KMSelectedKeyboardKey";
NSString *const kKMActiveKeyboardsKey = @"KMActiveKeyboardsKey";
NSString *const kKMSavedStoresKey = @"KMSavedStoresKey";
NSString *const kKMAlwaysShowOSKKey = @"KMAlwaysShowOSKKey";
NSString *const kKMUseVerboseLogging = @"KMUseVerboseLogging";
NSString *const kKMLegacyApps = @"KMLegacyApps";
NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification";
NSString *const kPackage = @"[Package]";
@ -64,6 +67,7 @@ typedef enum {
@synthesize alwaysShowOSK = _alwaysShowOSK;
id _lastServerWithOSKShowing = nil;
NSString* _keymanDataPath = nil;
- (id)init {
self = [super init];
@ -460,14 +464,37 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
return [userData boolForKey:kKMUseVerboseLogging];
}
/**
* Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards
*/
- (NSString *)keymanDataPath {
if(_keymanDataPath == nil) {
NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
_keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:_keymanDataPath]) {
[fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return _keymanDataPath;
}
/**
* Returns the list of user-default legacy apps
*/
- (NSArray *)legacyAppsUserDefaults {
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
return [userData arrayForKey:kKMLegacyApps];
}
/**
* Returns the root folder where keyboards are stored; currently the same
* as the keymanDataPath, but may diverge in future versions (possibly a sub-folder)
*/
- (NSString *)keyboardsPath {
if (_keyboardsPath == nil) {
NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
_keyboardsPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:_keyboardsPath]) {
[fm createDirectoryAtPath:_keyboardsPath withIntermediateDirectories:YES attributes:nil error:nil];
}
_keyboardsPath = [self keymanDataPath];
}
return _keyboardsPath;

View file

@ -42,22 +42,33 @@ NSRange _previousSelRange;
return self;
}
// This is the public initializer.
- (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender {
self.senderForDeleteBack = sender;
// TODO: Pages and Keynote (and possibly lots of other undiscovered apps that are otherwise compliant
// with Apple's IM framework) have a problem in that if the user selects a different font (or other
// formatting) and then types a sequence that causes characters to be added to the document and then
// subsequently replaced, the replacement causes the formatting decision to be forgotten. This can be
// "fixed" by treating them as legacy apps, but it causes other problems.
BOOL legacy = ([clientAppId isEqual: @"com.github.atom"] ||
/**
* Checks if the client app requires legacy input mode, first by checking the user defaults, if they exist,
* then, by our hard-coded list.
*/
- (BOOL)isClientAppLegacy:(NSString *)clientAppId {
NSArray *legacyAppsUserDefaults = [self.AppDelegate legacyAppsUserDefaults];
BOOL result = NO;
if(legacyAppsUserDefaults != nil) {
result = [self isClientAppLegacy:clientAppId fromArray:legacyAppsUserDefaults];
}
if(!result) {
// TODO: Pages and Keynote (and possibly lots of other undiscovered apps that are otherwise compliant
// with Apple's IM framework) have a problem in that if the user selects a different font (or other
// formatting) and then types a sequence that causes characters to be added to the document and then
// subsequently replaced, the replacement causes the formatting decision to be forgotten. This can be
// "fixed" by treating them as legacy apps, but it causes other problems.
result = ([clientAppId isEqual: @"com.github.atom"] ||
[clientAppId isEqual: @"com.collabora.libreoffice-free"] ||
[clientAppId isEqual: @"org.libreoffice.script"] ||
[clientAppId isEqual: @"com.axosoft.gitkraken"] ||
[clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] ||
[clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] ||
[clientAppId isEqual: @"org.sil.app.builder.dictionary.DictionaryAppBuilder"] ||
[clientAppId isEqual: @"com.microsoft.Word"] ||
//[clientAppId isEqual: @"com.microsoft.Word"] || // 2020-11-24[mcd]: Appears to work well in Word 16.43, disable legacy by default
[clientAppId isEqual: @"org.openoffice.script"] ||
[clientAppId isEqual: @"com.adobe.illustrator"] ||
[clientAppId isEqual: @"com.adobe.InDesign"] ||
@ -67,19 +78,43 @@ NSRange _previousSelRange;
[clientAppId isEqual: @"com.google.Chrome"] ||
[clientAppId hasPrefix: @"net.java"] ||
[clientAppId isEqual: @"com.Keyman.test.legacyInput"]
/*||[clientAppId isEqual: @"ro.sync.exml.Oxygen"] - Oxygen has worse problems */);
/*||[clientAppId isEqual: @"ro.sync.exml.Oxygen"] - Oxygen has worse problems */
);
}
// We used to default to NO, so these were the obvious exceptions. But then we realized that
// in any app, command keys can change the selection, so now we default to YES, and only have
// a few situations where we pretend it can't. This flag should probably be renamed to something
// like "disregardPossibleSelectionChanges".
// if ([clientAppId isEqual: @"com.google.Chrome"] ||
// [clientAppId isEqual: @"com.apple.Terminal"] ||
// [clientAppId isEqual: @"com.apple.dt.Xcode"]) {
// _clientSelectionCanChangeUnexpectedly = YES;
// }
return result;
}
// In Xcode, if Keyman is the active IM and is in "debugMode" 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
/**
* Checks user defaults array for a list of possible regexes to match a client app id
*/
- (BOOL)isClientAppLegacy:(NSString *)clientAppId fromArray:(NSArray *)legacyApps {
for(id legacyApp in legacyApps) {
if(![legacyApp isKindOfClass:[NSString class]]) {
NSLog(@"isClientAppLegacy:fromArray: LegacyApps user defaults array should contain only strings");
} else {
NSError *error = nil;
NSRange range = NSMakeRange(0, clientAppId.length);
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: (NSString *) legacyApp options: 0 error: &error];
if([regex matchesInString:clientAppId options:0 range:range]) {
return YES;
}
}
}
return NO;
}
// This is the public initializer.
- (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender {
self.senderForDeleteBack = sender;
BOOL legacy = [self isClientAppLegacy:clientAppId];
// In Xcode, if Keyman is the active IM and is in "debugMode" 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 ([self.AppDelegate debugMode] && [clientAppId isEqual: @"com.apple.dt.Xcode"]) {
NSLog(@"Sentry - Preparing to detect Easter egg.");
_easterEggForSentry = [[NSMutableString alloc] init];