Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mac): invoking Keyman Core from Keyman Engine for Mac 🍕 #8403

Merged
merged 58 commits into from
Oct 30, 2023

Conversation

sgschantz
Copy link
Contributor

@sgschantz sgschantz commented Mar 9, 2023

Initial steps in integration of Keyman Engine with Core:

  1. change to build script to trigger a build of Keyman Core library when building Keyman Engine
  2. Keyman Engine includes new wrapper classes that encapsulate the communication with Core
  3. Wrapper classes are dependent on Keyman Core headers and library
  4. Keyman Engine creates a new wrapper instance whenever the keyboard is changed (and it should be disposing of all resources related to the previous keyboard but this is not yet confirmed)

TODO

  • Keyman Engine should be disposing of all resources related to the previous keyboard but this is not yet confirmed

User Testing

  • TEST_AMHARIC:
    Install and select the Amharic keyboard. Run Pages and verify that
  1. typing ta produces
  2. typing t, left-arrow, a produces አት
  3. typing tt, left-arrow, a produces ታት
  4. typing tt, then mouse-clicking between the two characters, and then typing a produces ታት
  • TEST_IPA:
    Switch to the "IPA (SIL)" keyboard. Run Pages, type n> and verify that the result is ŋ

  • TEST_KOREAN:
    Switch to "Korean KORDA Jamo (SIL)" keyboard. Run Pages and type han, space, geul, space Verify that the result is "한글"

  • TEST_CONTEXT:
    Switch to the "IPA (SIL)" keyboard. Run Pages

  1. Type an, enter
  2. Type m, enter
  3. click after the letter n
  4. type>
    and verify that the output is

    m

@sgschantz sgschantz added the mac/ label Mar 9, 2023
@sgschantz sgschantz added this to the A17S8 milestone Mar 9, 2023
@sgschantz sgschantz self-assigned this Mar 9, 2023
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Mar 9, 2023

User Test Results

Test specification and instructions

  • TEST_AMHARIC (PASSED): Tested with the attached PR build (Keyman 17.0.61-alpha-test-8403) in macmini OS (Ventura) and here is my observations: 1. It is working fine in Notes App. 2. Working fine with Safari and Firefox browser. 3. Showing wrong outputs while testing it with Chrome browser and in LibreOffice Writer. (notes)
  • TEST_IPA (PASSED): 1. It is working fine in Notes App. 2. Working fine with Safari and Firefox browser. 3. Showing wrong outputs while testing it with Chrome browser and in LibreOffice Writer.
  • TEST_KOREAN (PASSED): 1. It is working fine in Notes App. 2. Working fine with Safari and Firefox browser. 3. Showing wrong outputs while testing it with Chrome browser and in LibreOffice Writer.
  • TEST_CONTEXT (PASSED): 1. It is working fine in Notes App. 2. Working fine with Safari and Firefox browser. 3. Showing wrong outputs while testing it with Chrome browser and in LibreOffice Writer.

Test Artifacts

@mcdurdin mcdurdin modified the milestones: A17S8, A17S9 Mar 18, 2023
@mcdurdin mcdurdin modified the milestones: A17S9, A17S10 Apr 4, 2023
@mcdurdin mcdurdin modified the milestones: A17S10, A17S11 Apr 15, 2023
@mcdurdin mcdurdin modified the milestones: A17S11, A17S12 Apr 29, 2023
@mcdurdin mcdurdin modified the milestones: A17S12, A17S13 May 14, 2023
@mcdurdin mcdurdin modified the milestones: A17S13, A17S14 May 28, 2023
@sgschantz sgschantz marked this pull request as ready for review October 5, 2023 02:15
@@ -105,6 +101,7 @@ - (BOOL)createEventTap {
return YES;
}

// TODO: investigate -- event handler for OSK only?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • TODO: investigate -- is eventTapFunction event handler for OSK only?

/**
* Keyman is copyright (C) SIL International. MIT License.
*
* CachedContext.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* CachedContext.h
* KMContext.h

-(instancetype)init NS_DESIGNATED_INITIALIZER;
-(instancetype)initWithString:(NSString*)initialContext;
-(void)resetContext:(NSString*)newContext;
-(void)addSubtring:(NSString*)string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-(void)addSubtring:(NSString*)string;
-(void)addSubstring:(NSString*)string;

-(void)resetContext:(NSString*)newContext {
[_context setString:[KMContext trimToMaximumContext:newContext]];
self.invalid = NO;
NSLog(@" ***CachedContext resetContext, resulting context = %@", _context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These NSLog functions should be enabled for debug only. Preference is a global debug logging function so we don't scatter #ifdef DEBUG everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can use the function tied to the AppDelegate:

  if (self.AppDelegate.debugMode) {
    NSLog(@"containedInNoncompliantAppLists: for app %@: %@", clientAppId, isAppNonCompliant?@"yes":@"no");
  }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to turn that into a helper function so we can keep the log calls to a single line?


-(void)clearContext {
_context.string = @"";
NSLog(@" ***CachedContext clearContext, resulting context = %@", _context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging

if (contextLength > 0) {
NSUInteger characterIndex = contextLength-1;
NSRange characterRange = [self.context rangeOfComposedCharacterSequenceAtIndex:characterIndex];
NSLog(@"KMContext deleteLastUnicodeCharacter, index = %lu, rangeOfLastCharacter = %@\n", characterIndex, NSStringFromRange(characterRange));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging

NSLog(@"KMContext deleteLastUnicodeCharacter, index = %lu, rangeOfLastCharacter = %@\n", characterIndex, NSStringFromRange(characterRange));
[self.context deleteCharactersInRange:characterRange];
}
NSLog(@" ***CachedContext deleteLastCodePoint, resulting context = %@", _context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not actually changing the logging for KMContext now because I expect to be deleting it and relying on the context stored in core instead.

if (newText.length > 0) {
[self addSubtring:newText];
}
NSLog(@" ***CachedContext replaceSubstring, resulting context = %@", _context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging

return self;
}

+(NSString*)trimToMaximumContext:(NSString*)initialContext {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't account for surrogate pairs

NSLog(@" ***CachedContext deleteLastCodePoint, resulting context = %@", _context);
}

-(void)replaceSubstring:(NSString*)newText count:(int)count {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is count number of codepoints or number of code units? We should document that

NSLog(@" ***CachedContext deleteLastCodePoint, resulting context = %@", _context);
}

-(void)replaceSubstring:(NSString*)newText count:(int)count {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is count number of codepoints or number of code units? We should document that

}

-(void)addSubtring:(NSString*)string {
[self.context appendString:string];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could make the context too long so we need to retrim afterwards


-(void)addSubtring:(NSString*)string {
[self.context appendString:string];
NSLog(@" ***CachedContext addSubtring, resulting context = %@", _context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging

Comment on lines 131 to 133
case MarkerAction:
[self appendMarker];
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A marker has a value between 1 and 0xFFFE, so you need to store UC_NONCHARACTER + the marker value.

mac/Keyman4MacIM/Keyman4MacIM/KMContext.m Outdated Show resolved Hide resolved
Comment on lines 42 to 44
/*
-(instancetype)initWithActions:(NSArray*)actions context: (KMContext *)context event: (NSEvent *)event client:(id) client;
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
-(instancetype)initWithActions:(NSArray*)actions context: (KMContext *)context event: (NSEvent *)event client:(id) client;
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed context which is not needed by KMCoreActionHandler

return action.actionType == EndAction;
}

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code to delete

Comment on lines 65 to 66
}
return self;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
return self;
}
return self;


// Creates a VK map to convert Mac VK codes to Windows VK codes
- (void)initVirtualKeyMapping {
VirtualKeyMap[MVK_A] = VK_KEY_A; // A
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate PR? alphabetize this because the current order is more than a little screwy!


// TODO: create once
// create option list
km_kbp_option_item coreEnvironment[6] = {0};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 6? Probably needs to be a define somewhere

coreOptionArray[3].scope = KM_KBP_OPT_ENVIRONMENT;
coreOptionArray[3].key = KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT;
//coreOptionArray[3].value = KeyboardGivesCtrlRAltForRAlt() ? u"1" : u"0";
coreOptionArray[3].value = u""; // const char16_t*, encoded as UTF-16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
coreOptionArray[3].value = u""; // const char16_t*, encoded as UTF-16
coreOptionArray[3].value = u"0"; // const char16_t*, encoded as UTF-16

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the dmg need to be checked in?

Copy link
Contributor Author

@sgschantz sgschantz Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this template determines the size of the dmg, and we were getting a build failure because it was too small after adding the core library dependencies. This change was to increase the size of the template dmg by 2MB.

@mcdurdin mcdurdin modified the milestones: A17S23, A17S24 Oct 15, 2023
Copy link
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's starting to come together. There are a number of comments from the previous review that remain unaddressed -- what's your plan for those?

typedef enum {BackspacesOnly,
CharactersOnly,
BackspaceBeforeCharacter,
CharacterBeforeBackspace,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think this can happen?


@interface KMCoreActionHandler ()

typedef enum {BackspacesOnly,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we apply the formatting fixes?

Comment on lines 404 to 408
if (self.appDelegate.lowLevelEventTap != nil) {
NSEvent *eventWithOriginalModifierFlags = [NSEvent keyEventWithType:event.type location:event.locationInWindow modifierFlags:self.appDelegate.currentModifierFlags timestamp:event.timestamp windowNumber:event.windowNumber context:event.context characters:event.characters charactersIgnoringModifiers:event.charactersIgnoringModifiers isARepeat:event.isARepeat keyCode:event.keyCode];
actions = [self.kme processEvent:eventWithOriginalModifierFlags];
[self.appDelegate logDebugMessage:@"processEventWithKeymanEngine, using AppDelegate.currentModifierFlags %lu, instead of event.modifiers = %lu", (unsigned long)self.appDelegate.currentModifierFlags, (unsigned long)event.modifierFlags];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these lines are very long, can you reformat?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will refactor during later cleanup

Comment on lines 709 to 710
if (self.appDelegate.contextChangedByLowLevelEvent)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (self.appDelegate.contextChangedByLowLevelEvent)
{
if (self.appDelegate.contextChangedByLowLevelEvent) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough re-review. I had missed a chunk of those. A few I am going to deal with when I use the core action API change and with one other cleanup. I need to address one review comment on modifier flags and then I think it will be ready.

Comment on lines 272 to 283
km_core_status result = km_core_context_get(context, &contextItemsArray);
if (result==KM_CORE_STATUS_OK) {
for (int i = 0; i < contextLength; i++) {
km_core_context_item contextItem = contextItemsArray[i];
if (contextItem.type == KM_CORE_CT_CHAR) {
const unichar unicodeChar = contextItem.character;
NSString *charString = [NSString stringWithCharacters:&unicodeChar length:1];
[contextString appendString:charString];
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct for surrogate pairs (i.e. >= U+10000)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unichar says it's UTF-16 so this wouldn't be correct. Line 277 is a narrowing of the type from 32 to 16 bits.

in C++ i have the Utf32CharToUtf16() macro to convert utf-32 to a 1-or-2-code unit string

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this! I had created a helper function for this conversion but neglected to use it here. I've added a unit test to ensure this works for a context string containing emojis.

@mcdurdin mcdurdin modified the milestones: A17S24, A17S25 Oct 27, 2023
…SMP text; also other clean up in response to review comments
Copy link
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. There are a few TODO items still that you may need to extract into issues, but lookin' good and ready to merge I think

@mcdurdin mcdurdin merged commit ae34ca3 into feature-macos-core Oct 30, 2023
6 of 16 checks passed
@mcdurdin mcdurdin deleted the feature/core/add-core-wrapper branch October 30, 2023 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants