Set Fn flag on macOS navigation keycodes so the next key isn't treated as Fn-modified

This commit is contained in:
Viktor Liu
2026-05-20 18:03:38 +02:00
parent b41d11bbbe
commit ef4ea2e311

View File

@@ -36,6 +36,15 @@ const (
kCGHIDEventTap int32 = 0
// kCGEventFlagMaskSecondaryFn is the CGEventFlags bit Apple sets when
// a key was activated via the Fn modifier on internal keyboards. The
// navigation cluster (ForwardDelete, Home, End, PageUp, PageDown,
// Help/Insert, arrows) lives in the Fn-shifted region of an Apple
// keyboard, so synthesising those keycodes without this bit leaves the
// system in a confused "Fn implied" state where the next plain
// letter is treated as a menu accelerator.
kCGEventFlagMaskSecondaryFn uint64 = 0x00800000
// kCGMouseEventClickState (event field 1) tells macOS how many
// consecutive clicks of this button have happened. Without it, a
// double click looks like two independent single clicks and apps
@@ -64,6 +73,7 @@ var (
cgEventCreateMouseEvent func(uintptr, int32, float64, float64, int32) uintptr
cgEventPost func(int32, uintptr)
cgEventSetIntegerValueField func(uintptr, int32, int64)
cgEventSetFlags func(uintptr, uint64)
// CGEventCreateScrollWheelEvent is variadic, call via SyscallN.
cgEventCreateScrollWheelEventAddr uintptr
@@ -125,6 +135,7 @@ func initDarwinInput() {
purego.RegisterLibFunc(&cgEventCreateMouseEvent, cg, "CGEventCreateMouseEvent")
purego.RegisterLibFunc(&cgEventPost, cg, "CGEventPost")
purego.RegisterLibFunc(&cgEventSetIntegerValueField, cg, "CGEventSetIntegerValueField")
purego.RegisterLibFunc(&cgEventSetFlags, cg, "CGEventSetFlags")
sym, err := purego.Dlsym(cg, "CGEventCreateScrollWheelEvent")
if err == nil {
@@ -397,16 +408,41 @@ func (m *MacInputInjector) InjectKeyScancode(scancode, keysym uint32, down bool)
m.postMacKey(src, vk, down)
}
// postMacKey emits a single key down/up event via Core Graphics.
// postMacKey emits a single key down/up event via Core Graphics. The
// Fn flag is attached for keycodes that live in the Fn-shifted region of
// an Apple keyboard so the system doesn't treat the next plain key as
// Fn-modified.
func (m *MacInputInjector) postMacKey(src uintptr, keycode uint16, down bool) {
event := cgEventCreateKeyboardEvent(src, keycode, down)
if event == 0 {
return
}
if isFnShiftedKeycode(keycode) && cgEventSetFlags != nil {
cgEventSetFlags(event, kCGEventFlagMaskSecondaryFn)
}
cgEventPost(kCGHIDEventTap, event)
cfRelease(event)
}
// isFnShiftedKeycode reports whether keycode is one of the Apple
// navigation/edit keys that hardware produces with the Fn modifier held.
func isFnShiftedKeycode(keycode uint16) bool {
switch keycode {
case 0x72, // Help / Insert
0x73, // Home
0x74, // PageUp
0x75, // ForwardDelete
0x77, // End
0x79, // PageDown
0x7B, // Left
0x7C, // Right
0x7D, // Down
0x7E: // Up
return true
}
return false
}
// InjectPointer simulates mouse movement and button events.
func (m *MacInputInjector) InjectPointer(buttonMask uint16, px, py, serverW, serverH int) {
wakeDisplay()