diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m index 7f06c529d2..b6f6521b43 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m @@ -41,7 +41,7 @@ NSMutableArray *servers; } - (NSUInteger)recognizedEvents:(id)sender { - return (NSEventMaskKeyDown); + return NSEventMaskKeyDown | NSEventMaskFlagsChanged; } - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { @@ -223,6 +223,7 @@ NSMutableArray *servers; [self.AppDelegate setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; [self.AppDelegate setContextBuffer:nil]; [self.AppDelegate setSelectedKeyboard:path]; + [self.AppDelegate loadSavedStores]; if (kvk != nil && self.AppDelegate.alwaysShowOSK) [self.AppDelegate showOSK]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 4fc27105ea..a7a2d63f34 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -89,6 +89,8 @@ extern NSString *const kWebSite; - (NSMenu *)menu; - (void)saveActiveKeyboards; +- (void)loadSavedStores; +- (void)saveStore:(NSNumber *)storeKey withValue:(NSString* )value; - (void)showAboutWindow; - (void)showOSK; - (void)showConfigurationWindow; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 335d2f2bed..3d157512a1 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -24,6 +24,7 @@ NSString *const kKMSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kKMActiveKeyboardsKey = @"KMActiveKeyboardsKey"; +NSString *const kKMSavedStoresKey = @"KMSavedStoresKey"; NSString *const kKMAlwaysShowOSKKey = @"KMAlwaysShowOSKKey"; NSString *const kKMUseVerboseLogging = @"KMUseVerboseLogging"; NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification"; @@ -81,7 +82,7 @@ id _lastServerWithOSKShowing = nil; forEventClass:kInternetEventClass andEventID:kAEGetURL]; - self.lowLevelEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, NSFlagsChangedMask | NSLeftMouseDown | NSLeftMouseUp, (CGEventTapCallBack)eventTapFunction, nil); + self.lowLevelEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, CGEventMaskBit(kCGEventFlagsChanged) | CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventLeftMouseUp), (CGEventTapCallBack)eventTapFunction, nil); if (!self.lowLevelEventTap) { NSLog(@"Can't tap into low level events!"); @@ -308,6 +309,53 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [_oskWindow.window setTitle:self.oskWindowTitle]; } +- (void)loadSavedStores { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + NSDictionary *allSavedStores = [userData dictionaryForKey:kKMSavedStoresKey]; + if (!allSavedStores) return; + NSDictionary *savedStores = [allSavedStores objectForKey:_selectedKeyboard]; + if (!savedStores) return; + + NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init]; + fmt.numberStyle = NSNumberFormatterDecimalStyle; + + for (NSString *key in savedStores) { + NSString *value = [savedStores objectForKey:key]; + NSUInteger storeID = [[fmt numberFromString:key] unsignedIntegerValue]; + [self.kme setStore:(DWORD)storeID withValue:value]; + } +} + +- (void)saveStore:(NSNumber *)storeKey withValue:(NSString* )value { + NSString *storeKeyStr = [storeKey stringValue]; + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + NSDictionary *allSavedStores = [userData dictionaryForKey:kKMSavedStoresKey]; + NSDictionary *savedStores; + + if (allSavedStores) { + savedStores = [allSavedStores objectForKey:_selectedKeyboard]; + } + + if (savedStores) { + NSMutableDictionary *newSavedStores = [savedStores mutableCopy]; + [newSavedStores setObject:value forKey:storeKeyStr]; + savedStores = newSavedStores; + } else { + savedStores = [[NSDictionary alloc] initWithObjectsAndKeys:value, storeKeyStr, nil]; + } + + if (allSavedStores) { + NSMutableDictionary *newAllSavedStores = [allSavedStores mutableCopy]; + [newAllSavedStores setObject:savedStores forKey:_selectedKeyboard]; + allSavedStores = newAllSavedStores; + } else { + allSavedStores = [[NSDictionary alloc] initWithObjectsAndKeys:savedStores, _selectedKeyboard, nil]; + } + + [userData setObject:allSavedStores forKey:kKMSavedStoresKey]; + [userData synchronize]; +} + - (NSString *)oskWindowTitle { if (_keyboardName == nil || !_keyboardName.length) return [NSString stringWithFormat:@"Keyman"]; @@ -765,6 +813,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [self setKvk:kvk]; [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; + [self loadSavedStores]; didSetKeyboard = YES; } @@ -800,6 +849,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; [self setContextBuffer:nil]; + [self loadSavedStores]; [self setSelectedKeyboard:path]; } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index cbaa1e1c06..01d9bb818c 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -425,6 +425,12 @@ NSRange _previousSelRange; else if ([actionType isEqualToString:Q_BEEP]) { [[NSSound soundNamed:@"Tink"] play]; } + else if ([actionType isEqualToString:Q_SAVEOPT]) { + NSDictionary *save = [action objectForKey:actionType]; + NSNumber *storeKey = [save allKeys][0]; + NSString *value = [save objectForKey:storeKey]; + [self.AppDelegate saveStore:storeKey withValue:value]; + } } return YES; } @@ -489,6 +495,11 @@ NSRange _previousSelRange; /*if (event.type == NSKeyDown) [self.AppDelegate handleKeyEvent:event];*/ + if (event.type == NSFlagsChanged) { + _contextOutOfDate = YES; + return NO; + } + if ((event.modifierFlags & NSEventModifierFlagCommand) == NSEventModifierFlagCommand) { [self handleCommand:event]; return NO; // We let the client app handle all Command-key events. diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSArray+Action.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSArray+Action.m index c305b0da49..b87ecbf4ba 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSArray+Action.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSArray+Action.m @@ -36,6 +36,9 @@ [self removeLastObject]; [self addObject:mAction]; } + else { + [self addObject:newAction]; + } } else if ([preActionType isEqualToString:Q_STR] && [newActionType isEqualToString:Q_BACK]) { NSString *preStr = [preAction objectForKey:preActionType]; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSString+XString.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSString+XString.m index d1f41104ba..61ddc70f66 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSString+XString.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/Categories/NSString+XString.m @@ -356,7 +356,7 @@ @implementation NSMutableString (XString) - (void)appendDeadkey:(NSUInteger)index { - NSString *dk = [NSString stringWithFormat:@"%C%C%lu", UC_SENTINEL, CODE_DEADKEY, index]; + NSString *dk = [NSString stringWithFormat:@"%C%C%C", UC_SENTINEL, CODE_DEADKEY, index]; [self appendString:dk]; } diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.h index 1d391eddd3..251b89d04b 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.h @@ -20,6 +20,7 @@ @property (strong, nonatomic) NSString *string; - (void)setDwSystemID:(DWORD)dwID; +- (id)copyWithZone:(NSZone *)zone; @end diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.m index b260980b9d..9f7c6dc9c6 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMCompStore.m @@ -21,6 +21,18 @@ return self; } +- (id)copyWithZone:(NSZone *)zone { + KMCompStore *newStore = [[[self class] allocWithZone:zone] init]; + + if (newStore) { + [newStore setDwSystemID:[self dwSystemID]]; + if ([self name]) newStore.name = [[NSString alloc] initWithString:[self name]]; + if ([self string]) newStore.string = [[NSString alloc] initWithString:[self string]]; + } + + return newStore; +} + - (NSString *)description { NSString *format = @"<%@:%p ID:0x%X DN:%@ N:%@ S:%@>"; NSString *str = [NSString stringWithFormat:format, diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h index e1fd6ec2dd..4e81fbcb56 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.h @@ -20,6 +20,7 @@ extern NSString *const Q_DEADKEY; extern NSString *const Q_NUL; extern NSString *const Q_BEEP; extern NSString *const Q_RETURN; +extern NSString *const Q_SAVEOPT; extern DWORD VKMap[0x80]; @@ -31,6 +32,7 @@ extern DWORD VKMap[0x80]; - (id)initWithKMX:(KMXFile *)kmx contextBuffer:(NSString *)ctxBuf; - (void)setContextBuffer:(NSString *)ctxBuf; - (NSString *)contextBuffer; +- (void)setStore:(DWORD)storeID withValue:(NSString *)value; - (NSArray *)processEvent:(NSEvent *)event; - (void)setUseVerboseLogging:(BOOL)useVerboseLogging; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m index cca2bf0738..ee123ce95d 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMEngine.m @@ -23,6 +23,7 @@ NSString *const Q_DEADKEY = @"Q_DEADKEY"; NSString *const Q_NUL = @"Q_NUL"; NSString *const Q_BEEP = @"Q_BEEP"; NSString *const Q_RETURN = @"Q_RETURN"; +NSString *const Q_SAVEOPT = @"Q_SAVEOPT"; DWORD VKMap[0x80]; @@ -103,6 +104,13 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; return _tmpCtxBuf; } +- (void)setStore:(DWORD)storeID withValue:(NSString *)value { + KMCompStore *store = [self.kmx.store objectAtIndex:storeID]; + KMCompStore *storeSaved = [self.kmx.storeSaved objectAtIndex:storeID]; + store.string = [[NSString alloc] initWithString:value]; + storeSaved.string = [[NSString alloc] initWithString:value]; +} + - (NSArray *)processEvent:(NSEvent *)event { NSArray *actions = nil; @@ -408,6 +416,8 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; if (!outKey || !outKey.length) return nil; + BOOL handledWithoutActions = NO; + for (int i = 0; i < outKey.length;) { unichar c = [outKey characterAtIndex:i]; if (c == UC_SENTINEL) { @@ -517,6 +527,42 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; i+=2; break; } + case CODE_SETOPT: { + DWORD x1 = [outKey characterAtIndex:i+2]-1; + DWORD x2 = [outKey characterAtIndex:i+3]-1; + KMCompStore *store1 = [self.kmx.store objectAtIndex:x1]; + KMCompStore *store2 = [self.kmx.store objectAtIndex:x2]; + store1.string = [[NSString alloc] initWithString:store2.string]; + handledWithoutActions = YES; + + i+=4; + break; + } + case CODE_RESETOPT: { + DWORD x1 = [outKey characterAtIndex:i+2]-1; + KMCompStore *store = [self.kmx.store objectAtIndex:x1]; + KMCompStore *storeSaved = [self.kmx.storeSaved objectAtIndex:x1]; + store.string = [[NSString alloc] initWithString:storeSaved.string]; + handledWithoutActions = YES; + i+=3; + break; + } + case CODE_SAVEOPT: { + DWORD x1 = [outKey characterAtIndex:i+2]-1; + KMCompStore *store = [self.kmx.store objectAtIndex:x1]; + KMCompStore *storeSaved = [self.kmx.storeSaved objectAtIndex:x1]; + storeSaved.string = [[NSString alloc] initWithString:store.string]; + + NSDictionary *actionObj = [[NSDictionary alloc] initWithObjectsAndKeys:[[NSString alloc] initWithString:store.string], [NSNumber numberWithUnsignedInteger:x1], nil]; + NSDictionary *action = [[NSDictionary alloc] initWithObjectsAndKeys:actionObj, Q_SAVEOPT, nil]; + if (!actions) + actions = [[NSMutableArray alloc] initWithObjects:action, nil]; + else + [actions addAction:action]; + + i+=3; + break; + } default: i+=2; break; @@ -538,6 +584,11 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; NSLog(@"tmpCtxBuf = \"%@\"", [self.tmpCtxBuf codeString]); } + if (!actions && handledWithoutActions) { + NSDictionary *action = [[NSDictionary alloc] initWithObjectsAndKeys:@"", Q_NUL, nil]; + actions = [[NSMutableArray alloc] initWithObjects:action, nil]; + } + return actions; } @@ -770,7 +821,7 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; DWORD index = [keyCtx characterAtIndex:i+2]; if ([ctx characterAtIndex:px] == UC_SENTINEL && [ctx characterAtIndex:px+1] == CODE_DEADKEY) { px+=2; - c = [[NSString stringWithFormat:@"%d", index] characterAtIndex:0]; + c = [[NSString stringWithFormat:@"%C", index] characterAtIndex:0]; unichar c2 = [ctx characterAtIndex:px]; if (c != c2) return 0; @@ -784,13 +835,21 @@ const NSString* kEasterEggKmxName = @"EnglishSpanish.kmx"; break; } case CODE_IFOPT: { - //DWORD x1 = [keyCtx characterAtIndex:i+2]-1; - //DWORD x2 = [keyCtx characterAtIndex:i+3]-1; - //DWORD x3 = [keyCtx characterAtIndex:i+4]-1; - //KMCompStore *store = [self.kmx.store objectAtIndex:x3]; - //i+=5; + DWORD x1 = [keyCtx characterAtIndex:i+2]-1; + DWORD x2 = [keyCtx characterAtIndex:i+3]-1; + DWORD x3 = [keyCtx characterAtIndex:i+4]-1; + KMCompStore *store1 = [self.kmx.store objectAtIndex:x1]; + KMCompStore *store2 = [self.kmx.store objectAtIndex:x3]; + + BOOL match = [store1.string isEqualToString:store2.string]; + if ((match && x2 == EQUAL) || (!match && x2 == NOT_EQUAL)) { + cx+=1000; + } + else { + return 0; + } - return 0; // CODE_IFOPT is not supported + i+=5; break; } case CODE_IFSYSTEMSTORE: { diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h index 287f4400e0..b94c8aab86 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h @@ -29,6 +29,7 @@ extern NSString *const kKMVisualKeyboardKey; @property (assign, nonatomic, readonly) BOOL isMnemonic; @property (assign, nonatomic, readonly) DWORD version; @property (strong, nonatomic, readonly) NSArray *store; +@property (strong, nonatomic, readonly) NSArray *storeSaved; @property (strong, nonatomic, readonly) NSArray *group; @property (strong, nonatomic, readonly) NSArray *startGroup; @property (assign, nonatomic, readonly) DWORD flags; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m index e25dc2d813..a467309a11 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m @@ -95,6 +95,7 @@ NSString *const kKMVisualKeyboardKey = @"KMVisualKeyboardKey"; } _store = [[NSArray alloc] initWithArray:mStore]; + _storeSaved = [[NSArray alloc] initWithArray:mStore copyItems:YES]; struct COMP_GROUP cmp_grp[cmp_kb.cxGroupArray]; [file seekToFileOffset:cmp_kb.dpGroupArray]; diff --git a/mac/history.md b/mac/history.md index 03828de9b6..5f23d80858 100644 --- a/mac/history.md +++ b/mac/history.md @@ -1,5 +1,10 @@ # Keyman for macOS Version History +## 2019-02-26 11.0.221 stable +* Add support for option stores (if/set/reset/save statements) (#1838) +* Fix bug where keyboards with more than nine deadkeys did not operate correctly (#1839) +* Fix out of date context buffer when using command keys (#1840) + ## 2019-02-25 11.0.220 stable * 11.0 Stable release