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): both option keys generate right alt if no left alt mapping #12458

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */; };
29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B4A0D32BF7675A00682049 /* KMLogs.m */; };
29B6FB732BC39DD60074BF7F /* TextApiComplianceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B6FB722BC39DD60074BF7F /* TextApiComplianceTests.m */; };
29BE9D872CA3C21900B67DE7 /* KMModifierMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */; };
29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; };
29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; };
37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; };
Expand Down Expand Up @@ -248,6 +249,8 @@
29B70A26283CB66B0086B580 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/KMKeyboardHelpWindowController.strings"; sourceTree = "<group>"; };
29B70A27283CB66C0086B580 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/MainMenu.strings"; sourceTree = "<group>"; };
29B70A28283CB6900086B580 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
29BE9D852CA3C21900B67DE7 /* KMModifierMapping.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMModifierMapping.h; sourceTree = "<group>"; };
29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMModifierMapping.m; sourceTree = "<group>"; };
29BFA761282936D4009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/KMAboutWindowController.strings; sourceTree = "<group>"; };
29BFA762282936D5009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/preferences.strings; sourceTree = "<group>"; };
29BFA763282936D5009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/KMInfoWindowController.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -582,6 +585,8 @@
98D6DA7D1A799FF400B09822 /* KMInputController.m */,
98A778C21A8C53BF00CF809D /* KMInputMethodAppDelegate.h */,
98A778C31A8C53BF00CF809D /* KMInputMethodAppDelegate.m */,
29BE9D852CA3C21900B67DE7 /* KMModifierMapping.h */,
29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */,
296105212C8E91C7007BF6B7 /* KMInputMethodLifecycle.h */,
296105222C8E91C7007BF6B7 /* KMInputMethodLifecycle.m */,
E21799031FC5B74D00F2D66A /* KMInputMethodEventHandler.h */,
Expand Down Expand Up @@ -1023,6 +1028,7 @@
290BC75E274F3FD7005CD1C3 /* KMKeyboardInfo.m in Sources */,
297A501728DF4D360074EB1B /* PrivacyWindowController.m in Sources */,
98BF92521BF03D4D0002126A /* KMAboutBGView.m in Sources */,
29BE9D872CA3C21900B67DE7 /* KMModifierMapping.m in Sources */,
98BF92571BF0431F0002126A /* KMAboutWindow.m in Sources */,
98BCD1F61B4E5998004E1AFD /* NSString+SuppMethods.m in Sources */,
);
Expand Down
18 changes: 12 additions & 6 deletions mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>
#import "KMModifierMapping.h"
#import "KMPackageReader.h"
#import "KMInputController.h"
#import "KMAboutWindowController.h"
Expand Down Expand Up @@ -61,22 +62,25 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
}

@property (nonatomic, strong) KMEngine *kme;
@property (nonatomic, strong) KMXFile *kmx;
// TODO: refactor and encapsulate below properties with current keyboard
@property (nonatomic, strong, readonly) KMXFile *kmx;
@property (nonatomic, strong, readonly) KMModifierMapping *modifierMapping;
@property (nonatomic, strong) KVKFile *kvk;
@property (nonatomic, strong) NSString *keyboardName;
@property (nonatomic, strong) NSString *selectedKeyboard;
@property (nonatomic, strong) NSImage *keyboardIcon;
// TODO: refactor above properties
@property (nonatomic, strong) NSString *keyboardsPath;
@property (nonatomic, strong) NSString *fontsPath;
@property (nonatomic, strong) NSMutableArray *kmxFileList;
@property (nonatomic, strong) NSString *selectedKeyboard;
@property (nonatomic, strong) NSMutableArray *activeKeyboards;
@property (assign) int numberOfKeyboardMenuItems;
@property (nonatomic, strong) NSMutableString *contextBuffer;
@property (nonatomic, assign) NSEventModifierFlags currentModifierFlags;
@property (nonatomic, assign) NSEventModifierFlags currentModifiers;
@property (nonatomic, assign) CFMachPortRef lowLevelEventTap;
@property (nonatomic, assign) CFRunLoopSourceRef runLoopEventSrc;
@property (nonatomic, assign) BOOL contextChangedByLowLevelEvent;
@property (nonatomic, strong) OSKWindowController *oskWindow;
@property (nonatomic, strong) NSString *keyboardName;
@property (nonatomic, strong) NSImage *keyboardIcon;
@property (nonatomic, strong) NSAlert *downloadInfoView;
@property (nonatomic, strong) NSProgressIndicator *progressIndicator;
@property (nonatomic, weak) KMInputController *inputController;
Expand All @@ -89,7 +93,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
@property (nonatomic, strong) NSString *downloadFilename;
@property (nonatomic, strong) NSMutableData *receivedData;
@property (nonatomic, assign) NSUInteger expectedBytes;
@property (nonatomic, assign) BOOL useNullChar;

- (NSMenu *)menu;
- (void)saveActiveKeyboards;
Expand All @@ -99,6 +102,9 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
- (void)showConfigurationWindow;
- (void)selectKeyboardFromMenu:(NSInteger)tag;
- (void)handleKeyEvent:(NSEvent *)event;
- (void)loadKeyboardFromKmxFile:(KMXFile *)kmx;
- (void)resetKmx;
- (NSEventModifierFlags) determineModifiers;
- (BOOL)unzipFile:(NSString *)filePath;
- (NSWindowController *)downloadKBWindow_;
- (NSWindowController *)aboutWindow_;
Expand Down
37 changes: 25 additions & 12 deletions mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#import "ZipArchive.h"
#import "KMPackageReader.h"
#import "KMPackageInfo.h"
#import "KMKeyboardInfo.h"
#import "PrivacyConsent.h"
#import "KMLogs.h"
@import Sentry;
Expand Down Expand Up @@ -52,6 +51,7 @@ @interface KMInputMethodAppDelegate ()
@implementation KMInputMethodAppDelegate
@synthesize kme = _kme;
@synthesize kmx = _kmx;
@synthesize modifierMapping = _modifierMapping;
@synthesize kvk = _kvk;
@synthesize keyboardName = _keyboardName;
@synthesize keyboardsPath = _keyboardsPath;
Expand Down Expand Up @@ -283,9 +283,9 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef

switch (type) {
case kCGEventFlagsChanged:
os_log_debug([KMLogs eventsLog], "eventTapFunction: system event kCGEventFlagsChanged to: %x", (int) sysEvent.modifierFlags);
appDelegate.currentModifierFlags = sysEvent.modifierFlags;
if (appDelegate.currentModifierFlags & NSEventModifierFlagCommand) {
os_log_debug([KMLogs eventsLog], "eventTapFunction: system event kCGEventFlagsChanged to: 0x%X", (int) sysEvent.modifierFlags);
appDelegate.currentModifiers = sysEvent.modifierFlags;
if (appDelegate.currentModifiers & NSEventModifierFlagCommand) {
appDelegate.contextChangedByLowLevelEvent = YES;
}
break;
Expand Down Expand Up @@ -389,16 +389,24 @@ - (KMPackageReader *)packageReader {
return _packageReader;
}

- (void)setKmx:(KMXFile *)kmx {
- (void)resetKmx {
_kmx = nil;
_modifierMapping = nil;
}

- (void)loadKeyboardFromKmxFile:(KMXFile *)kmx {
_kmx = kmx;
[self.kme setKmx:_kmx];
CoreKeyboardInfo *keyboardInfo = [self.kme loadKeyboardFromKmxFile:kmx];

os_log_info([KMLogs keyboardLog], "setKmx loaded keyboard, keyboard info: %{public}@", keyboardInfo);

NSString *keyboardFileName = [kmx.filePath lastPathComponent];
os_log_info([KMLogs keyboardLog], "setKmx to %{public}@", keyboardFileName);
_modifierMapping = [[KMModifierMapping alloc] init:keyboardInfo];

os_log_info([KMLogs keyboardLog], "modifierMapping bothOptionKeysGenerateRightAlt: %d", self.modifierMapping.bothOptionKeysGenerateRightAlt);

// assign custom keyboard tag in Sentry to default keyboard
[SentrySDK configureScope:^(SentryScope * _Nonnull scope) {
[scope setTagValue:keyboardFileName forKey:@"keyboard"];
[scope setTagValue:keyboardInfo.keyboardId forKey:@"keyboard"];
}];
}

Expand Down Expand Up @@ -787,7 +795,7 @@ - (void) setSelectedKeyboard:(NSString*)keyboardName inMenuItem:(NSMenuItem*) me
os_log_debug([KMLogs dataLog], "setSelectedKeyboard, keyboardName = '%{public}@', full path = '%{public}@'", keyboardName, fullPath);
[menuItem setState:NSOnState];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:fullPath];
[self setKmx:kmx];
[self loadKeyboardFromKmxFile:kmx];
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:fullPath];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
Expand Down Expand Up @@ -815,7 +823,7 @@ - (void) addKeyboardPlaceholderMenuItem {
NSString* placeholder = NSLocalizedString(@"no-keyboard-configured-menu-placeholder", nil);
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:placeholder action:NULL keyEquivalent:@""];
[self.menu insertItem:item atIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX];
[self setKmx:nil];
[self resetKmx];
[self setKvk:nil];
[self setKeyboardName:nil];
[self setKeyboardIcon:nil];
Expand Down Expand Up @@ -844,7 +852,7 @@ - (void)selectKeyboardFromMenu:(NSInteger)tag {
os_log_debug([KMLogs dataLog], "setSelectedKeyboard, keyboardName = '%{public}@', full path = '%{public}@'", path, fullPath);

KMXFile *kmx = [[KMXFile alloc] initWithFilePath:fullPath];
[self setKmx:kmx];
[self loadKeyboardFromKmxFile:kmx];
KVKFile *kvk = nil;
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:fullPath];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
Expand Down Expand Up @@ -1222,6 +1230,11 @@ - (void)handleKeyEvent:(NSEvent *)event {
[_oskWindow.oskView handleKeyEvent:event];
}

- (NSEventModifierFlags) determineModifiers {
NSEventModifierFlags originalModifiers = self.currentModifiers;
return [self.modifierMapping adjustModifiers:originalModifiers];
}

extern const CGKeyCode kProcessPendingBuffer;

// This could more easily and logically be done in the input method, but this
Expand Down
14 changes: 8 additions & 6 deletions mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,19 @@ - (BOOL) handleEventWithKeymanEngine:(NSEvent *)event in:(id) sender {
- (CoreKeyOutput*) processEventWithKeymanEngine:(NSEvent *)event in:(id) sender {
CoreKeyOutput* coreKeyOutput = nil;
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:[NSGraphicsContext currentContext] characters:event.characters charactersIgnoringModifiers:event.charactersIgnoringModifiers isARepeat:event.isARepeat keyCode:event.keyCode];
NSEventModifierFlags newModifiers = [self.appDelegate determineModifiers];
NSEvent *eventWithOriginalModifierFlags = [NSEvent keyEventWithType:event.type location:event.locationInWindow modifierFlags:newModifiers timestamp:event.timestamp windowNumber:event.windowNumber context:[NSGraphicsContext currentContext] characters:event.characters charactersIgnoringModifiers:event.charactersIgnoringModifiers isARepeat:event.isARepeat keyCode:event.keyCode];
coreKeyOutput = [self.kme processEvent:eventWithOriginalModifierFlags];
os_log_debug([KMLogs eventsLog], "processEventWithKeymanEngine, using AppDelegate.currentModifierFlags: %lu / 0x%lX, instead of event.modifiers = %lu / 0x%lX", self.appDelegate.currentModifierFlags, self.appDelegate.currentModifierFlags, event.modifierFlags, event.modifierFlags);
os_log_debug([KMLogs eventsLog], "processEventWithKeymanEngine, using newModifierFlag 0x%lX, instead of event.modifiers 0x%lX", newModifiers, event.modifierFlags);
}
else {
// Depending on the client app and the keyboard, using the passed-in event as it is should work okay.
// Keyboards that depend on chirality support will not work. And command-key actions that change the
// context might go undetected in some apps, resulting in errant behavior for subsequent typing.
/**
* Without the low level event tap, there will be no way to distinguish left and right option keys.
* Also command-key actions that should invalidate the context may go undetected.
* Have yet to determine which scenarios prevent an event tap from be created
*/
coreKeyOutput = [self.kme processEvent:event];
}
//os_log_debug([KMLogs eventsLog], "processEventWithKeymanEngine, coreKeyOutput = %{public}@", coreKeyOutput);
return coreKeyOutput;
}

Expand Down
20 changes: 20 additions & 0 deletions mac/Keyman4MacIM/Keyman4MacIM/KMModifierMapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Shawn Schantz on 2024-09-25.
*
*/

#import <Foundation/Foundation.h>
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>

NS_ASSUME_NONNULL_BEGIN

@interface KMModifierMapping : NSObject
-(instancetype)init:(CoreKeyboardInfo*)keyboardInfo;
-(BOOL)optionKeysUnused;
-(BOOL)bothOptionKeysGenerateRightAlt;
-(NSEventModifierFlags)adjustModifiers:(NSEventModifierFlags)originalModifiers;
@end

NS_ASSUME_NONNULL_END
72 changes: 72 additions & 0 deletions mac/Keyman4MacIM/Keyman4MacIM/KMModifierMapping.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Created by Shawn Schantz on 2024-09-25.
*
* Contains the logic that determines whether the right and left
* options keys both cause the same character to be generated or
* whether each option key results in a unique key combination.
*/

#import "KMModifierMapping.h"
#import <KeymanEngine4Mac/KeymanEngine4Mac.h>
#import "KMLogs.h"

@interface KMModifierMapping ()
@property (nonatomic, strong) CoreKeyboardInfo *keyboardInfo;
@end

@implementation KMModifierMapping

const NSEventModifierFlags LEFT_OPTION_MASK = 0x80120;
const NSEventModifierFlags RIGHT_OPTION_MASK = 0x80140;

+(BOOL)containsLeftOptionFlag:(NSEventModifierFlags)modifiers {
return (modifiers & LEFT_OPTION_MASK) == LEFT_OPTION_MASK;
}

-(instancetype)init:(CoreKeyboardInfo*)keyboardInfo {
self = [super init];
if (self) {
_keyboardInfo = keyboardInfo;
}
return self;
}

-(BOOL)optionKeysUnused {
return !(self.keyboardInfo.containsRightAlt ||
self.keyboardInfo.containsLeftAlt ||
self.keyboardInfo.containsAlt);
}

-(BOOL)bothOptionKeysGenerateRightAlt {
return (self.keyboardInfo.containsRightAlt || self.keyboardInfo.containsAlt) && !self.keyboardInfo.containsLeftAlt;
}

/**
* If necessary, change the modifiers so that it appears that the right option key was pressed instead of the
* left option key. This will trigger keyboard rules that are defined for right option because both option keys
* usually have the same meaning on the Mac.
*/
- (NSEventModifierFlags) adjustModifiers:(NSEventModifierFlags)originalModifiers {

NSEventModifierFlags newModifiers = originalModifiers;

if (self.bothOptionKeysGenerateRightAlt) {
// if original includes left option key, then replace it with the right option key
if ([KMModifierMapping containsLeftOptionFlag:originalModifiers]) {

// clear all left option bits
newModifiers = originalModifiers & ~LEFT_OPTION_MASK;

// set all right option bits
newModifiers = newModifiers | RIGHT_OPTION_MASK;
os_log_debug([KMLogs eventsLog], "adjustModifiers, changing from originalModifiers: 0x%lX to newModifiers: 0x%lX", originalModifiers, newModifiers);
}
} else {
os_log_debug([KMLogs eventsLog], "adjustModifiers, retaining originalModifiers: 0x%lX", (unsigned long)originalModifiers);
}
return newModifiers;
}

@end
1 change: 0 additions & 1 deletion mac/Keyman4MacIM/KeymanTests/TestAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ typedef void(^PostEventCallback)(CGEventRef eventToPost);
@property (nonatomic, strong) NSMutableString *contextBuffer;
@property (nonatomic, assign) CFMachPortRef lowLevelEventTap; // Always nil for tests
@property (nonatomic, assign) BOOL contextChangingEventDetected;
@property (nonatomic, assign) BOOL useNullChar;
@property (nonatomic, assign) CGKeyCode virtualKeyPosted;

// Helper method
Expand Down
2 changes: 1 addition & 1 deletion mac/Keyman4MacIM/KeymanTests/TestAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ - (KMEngine *)kme {

- (void)setKmx:(KMXFile *)kmx {
_kmx = kmx;
[self.kme setKmx:_kmx];
[self.kme loadKeyboardFromKmxFile:_kmx];
}

- (NSMutableString *)contextBuffer {
Expand Down
Loading
Loading