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

fix(mac): make modifiers operational in OSK #12556

Merged
merged 3 commits into from
Oct 25, 2024
Merged
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
27 changes: 23 additions & 4 deletions mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ - (NSString *)minimalVersionNumberString {

@interface KMInputMethodAppDelegate ()
@property (nonatomic, strong) KMPackageReader *packageReader;
@property BOOL receivedKeyDownFromOsk;
@property NSEventModifierFlags oskEventModifiers;
@end

@implementation KMInputMethodAppDelegate
Expand Down Expand Up @@ -299,7 +301,8 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
break;

case kCGEventKeyDown:
os_log_debug([KMLogs eventsLog], "Event tap keydown event, keyCode: %hu", sysEvent.keyCode);
appDelegate.receivedKeyDownFromOsk = NO;
os_log_debug([KMLogs eventsLog], "Event tap keydown event, keyCode: 0x%X (%d)", sysEvent.keyCode, sysEvent.keyCode);
// Pass back low-level backspace events to the input method event handler
// because some non-compliant apps do not allow us to see backspace events
// that we have generated (and we need to see them, for serialization
Expand All @@ -313,7 +316,12 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
if(sysEvent.keyCode == 255) {
os_log_debug([KMLogs eventsLog], "*** kKeymanEventKeyCode = 0xFF");
} else {
os_log_debug([KMLogs eventsLog], "*** other: %d(%x)", (char) sysEvent.keyCode, sysEvent.keyCode);
if ([OSKView isOskKeyDownEvent:event]) {
NSEventModifierFlags oskEventModifiers = [OSKView extractModifierFlagsFromOskEvent:event];
appDelegate.receivedKeyDownFromOsk = YES;
appDelegate.oskEventModifiers = oskEventModifiers;
os_log_debug([KMLogs eventsLog], "*** keydown event received from OSK, modifiers: 0x%lX",(unsigned long)oskEventModifiers);
}
}

switch(sysEvent.keyCode) {
Expand Down Expand Up @@ -1231,8 +1239,19 @@ - (void)handleKeyEvent:(NSEvent *)event {
}

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

if (self.receivedKeyDownFromOsk) {
modifierFlags = self.oskEventModifiers;
os_log_debug([KMLogs eventsLog], "--- use modifiers from OSK, oskEventModifiers: 0x%lX", (unsigned long)modifierFlags);
self.oskEventModifiers = 0;
} else {
NSEventModifierFlags originalModifiers = self.currentModifiers;
modifierFlags = [self.modifierMapping adjustModifiers:originalModifiers];
os_log_debug([KMLogs eventsLog], "--- use adjusted modifiers from current state: 0x%lX", (unsigned long)modifierFlags);
}

return modifierFlags;
}

extern const CGKeyCode kProcessPendingBuffer;
Expand Down
9 changes: 5 additions & 4 deletions mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ - (BOOL) handleEventWithKeymanEngine:(NSEvent *)event in:(id) sender {
- (CoreKeyOutput*) processEventWithKeymanEngine:(NSEvent *)event in:(id) sender {
CoreKeyOutput* coreKeyOutput = nil;
if (self.appDelegate.lowLevelEventTap != nil) {
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 newModifierFlag 0x%lX, instead of event.modifiers 0x%lX", newModifiers, event.modifierFlags);
NSEventModifierFlags modifierFlags = [self.appDelegate determineModifiers];
os_log_debug([KMLogs eventsLog], "processEventWithKeymanEngine, using modifierFlags 0x%lX, instead of event.modifiers 0x%lX", modifierFlags, event.modifierFlags);

NSEvent *eventWithAppropriateModifierFlags = [NSEvent keyEventWithType:event.type location:event.locationInWindow modifierFlags:modifierFlags 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:eventWithAppropriateModifierFlags];
}
else {
/**
Expand Down
4 changes: 2 additions & 2 deletions mac/Keyman4MacIM/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ target 'Keyman' do
# use_frameworks!

# Pods for Keyman
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.24.0'
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.38.0'

target 'KeymanTests' do
inherit! :search_paths
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.24.0'
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.38.0'
use_frameworks!
# Pods for testing
end
Expand Down
16 changes: 8 additions & 8 deletions mac/Keyman4MacIM/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
PODS:
- Sentry (8.24.0):
- Sentry/Core (= 8.24.0)
- Sentry/Core (8.24.0)
- Sentry (8.38.0-beta.1):
- Sentry/Core (= 8.38.0-beta.1)
- Sentry/Core (8.38.0-beta.1)

DEPENDENCIES:
- Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.24.0`)
- Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.38.0`)

EXTERNAL SOURCES:
Sentry:
:git: https://github.com/getsentry/sentry-cocoa.git
:tag: 8.24.0
:tag: 8.38.0

CHECKOUT OPTIONS:
Sentry:
:git: https://github.com/getsentry/sentry-cocoa.git
:tag: 8.24.0
:tag: 8.38.0

SPEC CHECKSUMS:
Sentry: 2f6baed15a3f8056b875fc903dc3dcb2903117f4
Sentry: 4d6027fbfde9ddc35e5c368292843097d039db5f

PODFILE CHECKSUM: 483593d0b294fc5751c2490061e01211db3f9ecc
PODFILE CHECKSUM: 19b128c35d9c5e59f90d09522c053de65096696a

COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ - (void)mouseUp:(NSEvent *)theEvent {
}

-(void)processKeyClick {
os_log_debug([KMELogs oskLog], "KeyView processKeyClick");
[self.target keyAction:self];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

@property (weak, nonatomic) KVKFile *kvk;

+ (BOOL)isOskKeyDownEvent:(CGEventRef)event;
+ (NSEventModifierFlags)extractModifierFlagsFromOskEvent:(CGEventRef)event;
- (void)handleKeyEvent:(NSEvent *)event;
- (void)setShiftState:(BOOL)shiftState;
- (void)setOskShiftState:(BOOL)oskShiftState;
Expand All @@ -25,6 +27,7 @@
- (void)setOskCtrlState:(BOOL)oskCtrlState;
- (void)resetOSK;
- (void)resizeOSKLayout;
- (int64_t)createOskEventUserData;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include <Carbon/Carbon.h>
#import "KMELogs.h"

const int64_t OSK_EVENT_FLAG = 0x8000000000000000;
Copy link
Member

Choose a reason for hiding this comment

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

How was this flag selected?

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 am copying the modifier flags into the lower 32 bits and saving the upper 32 bits to mark this as our custom user data. Since it defaults to zero otherwise, I am just setting the upper-most bit of the upper 32, and I can use the other 31 bits in the future if I need to pass any other info from the OSK through to the event tap.

const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF;

@interface OSKView()
@property (nonatomic, strong) NSArray *oskLayout;
@property (nonatomic, strong) NSArray *oskDefaultNKeys;
Expand All @@ -31,6 +34,29 @@ @interface OSKView()
@implementation OSKView
@synthesize tag;

/**
* Check the kCGEventSourceUserData field of the event and determine whether the event was
* generated by the OSK.
*/
+ (BOOL)isOskKeyDownEvent:(CGEventRef)event {
// check to see if this event was generated by the OSK
int64_t eventUserData = CGEventGetIntegerValueField(event, kCGEventSourceUserData);
return ((eventUserData & OSK_EVENT_FLAG) == OSK_EVENT_FLAG);
}

/**
* If the event was generated by the OSK, then return the modifier flags stored in the kCGEventSourceUserData
* field at the time the event was generated
*/
+ (NSEventModifierFlags)extractModifierFlagsFromOskEvent:(CGEventRef)event {
NSEventModifierFlags modifierFlags = 0;
int64_t eventUserData = CGEventGetIntegerValueField(event, kCGEventSourceUserData);
if ([OSKView isOskKeyDownEvent:event]) {
modifierFlags = (NSEventModifierFlags) eventUserData & OSK_EVENT_MODIFIER_MASK;
}
return modifierFlags;
}

- (id)initWithFrame:(NSRect)frame {
os_log_debug([KMELogs oskLog], "OSKView initWithFrame: %{public}@", NSStringFromRect(frame));
self = [super initWithFrame:frame];
Expand All @@ -42,6 +68,12 @@ - (id)initWithFrame:(NSRect)frame {
return self;
}

- (NSString *)description {
NSString *format = @"<shiftState:%d oskShiftState:%d altState:%d oskAltState:%d ctrlState:%d oskCtrlState:%d>";
NSString *str = [NSString stringWithFormat:format, _shiftState, _oskShiftState, _altState, _oskAltState, _ctrlState, _oskCtrlState];
return str;
}

- (void)drawRect:(NSRect)rect {
os_log_debug([KMELogs oskLog], "OSKView drawRect: %{public}@", NSStringFromRect(rect));

Expand Down Expand Up @@ -411,30 +443,24 @@ - (void)resizeOSKLayout {
- (void)keyAction:(id)sender {
KeyView *keyView = (KeyView *)sender;
NSUInteger keyCode = [keyView.key keyCode];
os_log_debug([KMELogs oskLog], "OSKView keyAction keyView: %{public}@", keyView);
os_log_debug([KMELogs oskLog], "OSKView keyAction, self: %{public}@, keyView: %{public}@", self, keyView);
if (keyCode < 0x100) {
NSRunningApplication *app = NSWorkspace.sharedWorkspace.frontmostApplication;
pid_t processId = app.processIdentifier;
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
CGEventRef keyDownEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, true);
CGEventRef keyUpEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, false);
if (self.shiftState || self.oskShiftState) {
CGEventSetFlags(keyDownEvent, CGEventGetFlags(keyDownEvent) | kCGEventFlagMaskShift);
CGEventSetFlags(keyUpEvent, CGEventGetFlags(keyUpEvent) | kCGEventFlagMaskShift);
}
if (self.altState || self.oskAltState) {
CGEventSetFlags(keyDownEvent, CGEventGetFlags(keyDownEvent) | kCGEventFlagMaskAlternate);
CGEventSetFlags(keyUpEvent, CGEventGetFlags(keyUpEvent) | kCGEventFlagMaskAlternate);
}
if (self.ctrlState || self.oskCtrlState) {
CGEventSetFlags(keyDownEvent, CGEventGetFlags(keyDownEvent) | kCGEventFlagMaskControl);
CGEventSetFlags(keyUpEvent, CGEventGetFlags(keyUpEvent) | kCGEventFlagMaskControl);
}

CGEventSetIntegerValueField(keyDownEvent, kCGEventSourceUserData, [self createOskEventUserData]);

os_log_debug([KMELogs oskLog], "OSKView keyAction, keyDownEvent: %{public}@, modifier flags: 0x%llX", keyDownEvent, CGEventGetFlags(keyDownEvent));

CGEventPostToPid(processId, keyDownEvent);
CGEventPostToPid(processId, keyUpEvent);
CFRelease(source);
CFRelease(keyDownEvent);
CFRelease(keyUpEvent);

CFRelease(source);
}
else {
if (keyCode == MVK_LEFT_SHIFT || keyCode == MVK_RIGHT_SHIFT) {
Expand All @@ -449,6 +475,37 @@ - (void)keyAction:(id)sender {
}
}

/**
* Create the 64-bit value to pass in the kCGEventSourceUserData field of the generated CGEvent
* The upper-most bit is set to distinguish it from the default value of zero, and the lower 32 bits
* store the modifier state of the OSK at the time that the keydown event was generated.
* This data is safely conveys the modifier information to the eventTap function.
* If the modifiers themselves were instead set by calling the function `CGEventSetFlags`
* directly on the generated CGEvent, then there would be no way to distinguish between the left
* and right option keys.
*
*/
- (int64_t) createOskEventUserData {
// set bit to identify this user data as originating from the OSK
int64_t oskEventData = OSK_EVENT_FLAG;

if (self.oskShiftState || self.shiftState) {
oskEventData = oskEventData | kCGEventFlagMaskShift;
}
/**
* Both left and right alt keys on the OSK cause oskAltState to be true without distinction of left or right.
* However, the generated event is a right alt so that it will trigger the right alt rules in the Keyman keyboard.
*/
if (self.oskAltState || self.altState) {
oskEventData = oskEventData | MK_RIGHT_ALT_MASK;
}
if (self.oskCtrlState || self.oskCtrlState) {
oskEventData = oskEventData | kCGEventFlagMaskControl;
}

return oskEventData;
}

- (void)handleKeyEvent:(NSEvent *)event {
os_log_debug([KMELogs oskLog], "OSKView handleKeyEvent event.type: %lu", event.type);
NSView *view = [self viewWithTag:event.keyCode|0x1000];
Expand All @@ -467,7 +524,7 @@ - (void)setKeyPressedOff:(KeyView *)keyView {
}

- (void)setKeyLabels:(BOOL)shift alt:(BOOL)alt ctrl:(BOOL)ctrl {
os_log_debug([KMELogs keyLog], "OSKView setKeyLabels, shift: %d, alt: %d, ctrl: %d", shift, alt, ctrl);
os_log_debug([KMELogs oskLog], "OSKView setKeyLabels, shift: %d, alt: %d, ctrl: %d", shift, alt, ctrl);
[self resetKeyLabels];
NSMutableArray *mKeys = [[self keyTags] mutableCopy];
NSArray *nkeys = [self.kvk keys];
Expand Down
Loading