mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
app: [macos] don't relay key events handled by the IME
Widgets such as Editor use certain key events such as the backspace key to implement text editing. On macOS, such key events are sometimes used by an input method, and in those cases the key effect would be applied twice: first by the IME and then the Editor. Report such key events through the doCommandBySelector callback, which receives key events not handled by the IME. References: https://todo.sr.ht/~eliasnaur/gio/616 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+69
-10
@@ -39,6 +39,12 @@ import (
|
|||||||
#define MOUSE_DOWN 3
|
#define MOUSE_DOWN 3
|
||||||
#define MOUSE_SCROLL 4
|
#define MOUSE_SCROLL 4
|
||||||
|
|
||||||
|
#define KEY_DELETE_BACKWARD 0x7f
|
||||||
|
#define KEY_RETURN 0xd
|
||||||
|
#define KEY_ENTER 0x3
|
||||||
|
#define KEY_ESCAPE 0x1b
|
||||||
|
#define KEY_TAB 0x09
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||||
@@ -343,6 +349,8 @@ type window struct {
|
|||||||
|
|
||||||
scale float32
|
scale float32
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
|
cmdKeysDown map[key.Name]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// launched is closed when applicationDidFinishLaunching is called.
|
// launched is closed when applicationDidFinishLaunching is called.
|
||||||
@@ -551,7 +559,16 @@ func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger,
|
|||||||
}
|
}
|
||||||
w := windowFor(h)
|
w := windowFor(h)
|
||||||
for _, k := range str {
|
for _, k := range str {
|
||||||
if n, ok := convertKey(k); ok {
|
n, ok := convertKey(k)
|
||||||
|
if !ok && ks == key.Release {
|
||||||
|
// Key release events are not reported by onCommandBySelector.
|
||||||
|
n, ok = convertCommandKey(k)
|
||||||
|
if ok {
|
||||||
|
_, ok = w.cmdKeysDown[n]
|
||||||
|
delete(w.cmdKeysDown, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
w.ProcessEvent(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: n,
|
Name: n,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
@@ -561,6 +578,29 @@ func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export gio_onCommandBySelector
|
||||||
|
func gio_onCommandBySelector(h C.uintptr_t, k C.int, ti C.double, mods C.NSUInteger) {
|
||||||
|
w := windowFor(h)
|
||||||
|
w.commandKey(rune(k), ti, mods)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) commandKey(k rune, ti C.double, mods C.NSUInteger) bool {
|
||||||
|
n, ok := convertCommandKey(k)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if w.cmdKeysDown == nil {
|
||||||
|
w.cmdKeysDown = make(map[key.Name]struct{})
|
||||||
|
}
|
||||||
|
w.cmdKeysDown[n] = struct{}{}
|
||||||
|
w.ProcessEvent(key.Event{
|
||||||
|
Modifiers: convertMods(mods),
|
||||||
|
Name: n,
|
||||||
|
State: key.Press,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
//export gio_onText
|
//export gio_onText
|
||||||
func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
|
func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
|
||||||
str := nsstringToString(cstr)
|
str := nsstringToString(cstr)
|
||||||
@@ -749,8 +789,19 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_insertText
|
//export gio_insertText
|
||||||
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange, ti C.double, mods C.NSUInteger) {
|
||||||
w := windowFor(h)
|
w := windowFor(h)
|
||||||
|
str := nsstringToString(cstr)
|
||||||
|
// macOS IME in some cases calls insertText for command keys such as backspace
|
||||||
|
// instead of doCommandBySelector.
|
||||||
|
hadCommands := false
|
||||||
|
for _, r := range str {
|
||||||
|
hadCommands = w.commandKey(r, ti, mods) || hadCommands
|
||||||
|
}
|
||||||
|
if hadCommands {
|
||||||
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
|
return
|
||||||
|
}
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.compose
|
rng := state.compose
|
||||||
if rng.Start == -1 {
|
if rng.Start == -1 {
|
||||||
@@ -762,7 +813,6 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
|||||||
End: state.RunesIndex(int(crng.location + crng.length)),
|
End: state.RunesIndex(int(crng.location + crng.length)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
str := nsstringToString(cstr)
|
|
||||||
w.w.EditorReplace(rng, str)
|
w.w.EditorReplace(rng, str)
|
||||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
start := rng.Start
|
start := rng.Start
|
||||||
@@ -957,10 +1007,10 @@ func osMain() {
|
|||||||
C.gio_main()
|
C.gio_main()
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKey(k rune) (key.Name, bool) {
|
func convertCommandKey(k rune) (key.Name, bool) {
|
||||||
var n key.Name
|
var n key.Name
|
||||||
switch k {
|
switch k {
|
||||||
case 0x1b:
|
case C.KEY_ESCAPE:
|
||||||
n = key.NameEscape
|
n = key.NameEscape
|
||||||
case C.NSLeftArrowFunctionKey:
|
case C.NSLeftArrowFunctionKey:
|
||||||
n = key.NameLeftArrow
|
n = key.NameLeftArrow
|
||||||
@@ -970,22 +1020,33 @@ func convertKey(k rune) (key.Name, bool) {
|
|||||||
n = key.NameUpArrow
|
n = key.NameUpArrow
|
||||||
case C.NSDownArrowFunctionKey:
|
case C.NSDownArrowFunctionKey:
|
||||||
n = key.NameDownArrow
|
n = key.NameDownArrow
|
||||||
case 0xd:
|
case C.KEY_RETURN:
|
||||||
n = key.NameReturn
|
n = key.NameReturn
|
||||||
case 0x3:
|
case C.KEY_ENTER:
|
||||||
n = key.NameEnter
|
n = key.NameEnter
|
||||||
case C.NSHomeFunctionKey:
|
case C.NSHomeFunctionKey:
|
||||||
n = key.NameHome
|
n = key.NameHome
|
||||||
case C.NSEndFunctionKey:
|
case C.NSEndFunctionKey:
|
||||||
n = key.NameEnd
|
n = key.NameEnd
|
||||||
case 0x7f:
|
case C.KEY_DELETE_BACKWARD, 0x08:
|
||||||
n = key.NameDeleteBackward
|
n = key.NameDeleteBackward
|
||||||
case C.NSDeleteFunctionKey:
|
case C.NSDeleteFunctionKey:
|
||||||
n = key.NameDeleteForward
|
n = key.NameDeleteForward
|
||||||
|
case C.KEY_TAB, 0x19:
|
||||||
|
n = key.NameTab
|
||||||
case C.NSPageUpFunctionKey:
|
case C.NSPageUpFunctionKey:
|
||||||
n = key.NamePageUp
|
n = key.NamePageUp
|
||||||
case C.NSPageDownFunctionKey:
|
case C.NSPageDownFunctionKey:
|
||||||
n = key.NamePageDown
|
n = key.NamePageDown
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertKey(k rune) (key.Name, bool) {
|
||||||
|
var n key.Name
|
||||||
|
switch k {
|
||||||
case C.NSF1FunctionKey:
|
case C.NSF1FunctionKey:
|
||||||
n = key.NameF1
|
n = key.NameF1
|
||||||
case C.NSF2FunctionKey:
|
case C.NSF2FunctionKey:
|
||||||
@@ -1010,8 +1071,6 @@ func convertKey(k rune) (key.Name, bool) {
|
|||||||
n = key.NameF11
|
n = key.NameF11
|
||||||
case C.NSF12FunctionKey:
|
case C.NSF12FunctionKey:
|
||||||
n = key.NameF12
|
n = key.NameF12
|
||||||
case 0x09, 0x19:
|
|
||||||
n = key.NameTab
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
n = key.NameSpace
|
n = key.NameSpace
|
||||||
default:
|
default:
|
||||||
|
|||||||
+50
-4
@@ -15,6 +15,7 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
|
@property NSEvent *lastKeyDown;
|
||||||
@property uintptr_t handle;
|
@property uintptr_t handle;
|
||||||
@property BOOL presentWithTrans;
|
@property BOOL presentWithTrans;
|
||||||
@end
|
@end
|
||||||
@@ -132,7 +133,10 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||||
}
|
}
|
||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
|
// Stash the event for use by doCommandBySelector.
|
||||||
|
self.lastKeyDown = event;
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
|
self.lastKeyDown = nil;
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||||
}
|
}
|
||||||
@@ -143,9 +147,47 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
- (void)insertText:(id)string {
|
- (void)insertText:(id)string {
|
||||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)sel {
|
- (void)doCommandBySelector:(SEL)action {
|
||||||
// Don't pass commands up the responder chain.
|
NSEvent *event = self.lastKeyDown;
|
||||||
// They will end up in a beep.
|
if (event == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int key = 0;
|
||||||
|
if (action == @selector(deleteBackward:)) {
|
||||||
|
key = KEY_DELETE_BACKWARD;
|
||||||
|
} else if (action == @selector(deleteForward:)) {
|
||||||
|
key = NSDeleteFunctionKey;
|
||||||
|
} else if (action == @selector(insertNewline:)) {
|
||||||
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
|
if ([keys isEqualToString:@"\r"]) {
|
||||||
|
key = KEY_RETURN;
|
||||||
|
} else {
|
||||||
|
key = KEY_ENTER;
|
||||||
|
}
|
||||||
|
} else if (action == @selector(moveUp:)) {
|
||||||
|
key = NSUpArrowFunctionKey;
|
||||||
|
} else if (action == @selector(moveDown:)) {
|
||||||
|
key = NSDownArrowFunctionKey;
|
||||||
|
} else if (action == @selector(moveLeft:)) {
|
||||||
|
key = NSLeftArrowFunctionKey;
|
||||||
|
} else if (action == @selector(moveRight:)) {
|
||||||
|
key = NSRightArrowFunctionKey;
|
||||||
|
} else if (action == @selector(cancelOperation:)) {
|
||||||
|
key = KEY_ESCAPE;
|
||||||
|
} else if (action == @selector(insertTab:)) {
|
||||||
|
key = KEY_TAB;
|
||||||
|
} else if (action == @selector(scrollToBeginningOfDocument:)) {
|
||||||
|
key = NSHomeFunctionKey;
|
||||||
|
} else if (action == @selector(scrollToEndOfDocument:)) {
|
||||||
|
key = NSEndFunctionKey;
|
||||||
|
} else if (action == @selector(scrollPageUp:)) {
|
||||||
|
key = NSPageUpFunctionKey;
|
||||||
|
} else if (action == @selector(scrollPageDown:)) {
|
||||||
|
key = NSPageDownFunctionKey;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gio_onCommandBySelector(self.handle, key, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
@@ -183,6 +225,10 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
}
|
}
|
||||||
- (void)insertText:(id)string
|
- (void)insertText:(id)string
|
||||||
replacementRange:(NSRange)replaceRange {
|
replacementRange:(NSRange)replaceRange {
|
||||||
|
NSEvent *event = self.lastKeyDown;
|
||||||
|
if (event == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
NSString *str;
|
NSString *str;
|
||||||
// string is either an NSAttributedString or an NSString.
|
// string is either an NSAttributedString or an NSString.
|
||||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||||
@@ -190,7 +236,7 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||||
return gio_characterIndexForPoint(self.handle, p);
|
return gio_characterIndexForPoint(self.handle, p);
|
||||||
|
|||||||
Reference in New Issue
Block a user