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
+37 -1
View File
@@ -36,6 +36,15 @@ const (
kCGHIDEventTap int32 = 0 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 // kCGMouseEventClickState (event field 1) tells macOS how many
// consecutive clicks of this button have happened. Without it, a // consecutive clicks of this button have happened. Without it, a
// double click looks like two independent single clicks and apps // double click looks like two independent single clicks and apps
@@ -64,6 +73,7 @@ var (
cgEventCreateMouseEvent func(uintptr, int32, float64, float64, int32) uintptr cgEventCreateMouseEvent func(uintptr, int32, float64, float64, int32) uintptr
cgEventPost func(int32, uintptr) cgEventPost func(int32, uintptr)
cgEventSetIntegerValueField func(uintptr, int32, int64) cgEventSetIntegerValueField func(uintptr, int32, int64)
cgEventSetFlags func(uintptr, uint64)
// CGEventCreateScrollWheelEvent is variadic, call via SyscallN. // CGEventCreateScrollWheelEvent is variadic, call via SyscallN.
cgEventCreateScrollWheelEventAddr uintptr cgEventCreateScrollWheelEventAddr uintptr
@@ -125,6 +135,7 @@ func initDarwinInput() {
purego.RegisterLibFunc(&cgEventCreateMouseEvent, cg, "CGEventCreateMouseEvent") purego.RegisterLibFunc(&cgEventCreateMouseEvent, cg, "CGEventCreateMouseEvent")
purego.RegisterLibFunc(&cgEventPost, cg, "CGEventPost") purego.RegisterLibFunc(&cgEventPost, cg, "CGEventPost")
purego.RegisterLibFunc(&cgEventSetIntegerValueField, cg, "CGEventSetIntegerValueField") purego.RegisterLibFunc(&cgEventSetIntegerValueField, cg, "CGEventSetIntegerValueField")
purego.RegisterLibFunc(&cgEventSetFlags, cg, "CGEventSetFlags")
sym, err := purego.Dlsym(cg, "CGEventCreateScrollWheelEvent") sym, err := purego.Dlsym(cg, "CGEventCreateScrollWheelEvent")
if err == nil { if err == nil {
@@ -397,16 +408,41 @@ func (m *MacInputInjector) InjectKeyScancode(scancode, keysym uint32, down bool)
m.postMacKey(src, vk, down) 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) { func (m *MacInputInjector) postMacKey(src uintptr, keycode uint16, down bool) {
event := cgEventCreateKeyboardEvent(src, keycode, down) event := cgEventCreateKeyboardEvent(src, keycode, down)
if event == 0 { if event == 0 {
return return
} }
if isFnShiftedKeycode(keycode) && cgEventSetFlags != nil {
cgEventSetFlags(event, kCGEventFlagMaskSecondaryFn)
}
cgEventPost(kCGHIDEventTap, event) cgEventPost(kCGHIDEventTap, event)
cfRelease(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. // InjectPointer simulates mouse movement and button events.
func (m *MacInputInjector) InjectPointer(buttonMask uint16, px, py, serverW, serverH int) { func (m *MacInputInjector) InjectPointer(buttonMask uint16, px, py, serverW, serverH int) {
wakeDisplay() wakeDisplay()