Simulating Fn (Globe) + Arrow Key Events Ignoring Fn Modifier

I want to simulate the pressing of Fn (Globe) + Control + arrow keys combo, but I’m encountering an issue where the Fn modifier seems to be ignored, and the system behaves as if only Control + arrow keys are pressed.

In macOS, the combination of Fn + Right Arrow (key code 124) is treated as End (key code 119), and even that didn’t have expected behavior. Instead of moving the window to the right edge of the screen on Sequoia, it switches to the next space, which is the default behavior for Control + Right Arrow.

Demo:

(I included Fn + Control + C to show that centering the window works for example.)

import SwiftUI

@main
struct LittleKeypressDemo: App {
  var body: some Scene {
    Window("Keypress Demo", id: "keypress-demo") {
      ContentView()
    }
    .windowStyle(.hiddenTitleBar)
    .windowResizability(.contentSize)
    .windowBackgroundDragBehavior(.enabled)
  }
}

struct ContentView: View {
  var body: some View {
    VStack(spacing: 20) {
      KeyPressButton(icon: "arrowtriangle.right.fill", keyCode: 124)
      KeyPressButton(icon: "arrow.down.to.line", keyCode: 119)
      KeyPressButton(label: "C", keyCode: 8)
    }
    .padding()
  }
}

struct KeyPressButton: View {
  let icon: String?
  let label: String?
  let keyCode: CGKeyCode

  init(icon: String? = nil, label: String? = nil, keyCode: CGKeyCode) {
    self.icon = icon
    self.label = label
    self.keyCode = keyCode
  }

  var body: some View {
    Button(action: { simulateKeyPress(keyCode) }) {
      HStack {
        Image(systemName: "globe")
        Image(systemName: "control")
        if let icon = icon {
          Image(systemName: icon)
        } else if let label = label {
          Text(label)
        }
      }
    }
    .buttonStyle(.bordered)
    .controlSize(.large)
  }
}

func simulateKeyPress(_ keyCode: CGKeyCode) {
  let fnKey = VirtualKey(keyCode: 63, flags: .maskSecondaryFn)
  let controlKey = VirtualKey(keyCode: 59, flags: [.maskControl, .maskSecondaryFn])
  let targetKey = VirtualKey(keyCode: keyCode, flags: [.maskControl, .maskSecondaryFn])

  [fnKey, controlKey, targetKey].forEach { $0.pressAndRelease() }
}

struct VirtualKey {
  let keyCode: CGKeyCode
  let flags: CGEventFlags

  func pressAndRelease() {
    postKeyEvent(keyDown: true)
    postKeyEvent(keyDown: false)
  }

  private func postKeyEvent(keyDown: Bool) {
    guard let event = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: keyDown) else { return }
    event.flags = flags
    event.post(tap: .cghidEventTap)
  }
}

Expected behavior: Simulating the key combo Fn + Control + Right Arrow on macOS Sequoia should move the current window to the right half of the screen, instead of switching to the next desktop space.

Questions: Is CGEventFlags.maskSecondaryFn enough to simulate the Fn key in combination with Control and the arrow keys? Are there alternative approaches or workarounds to correctly simulate this behavior? What’s the obvious thing I missing?

(Btw., window management is completely irrelevant here. I’m specifically asking about simulating these key presses.)

Any insights or suggestions would be greatly appreciated.

Thank you.

I made adjustments to the example code since the original had leftover elements from experimentation.

import SwiftUI

@main
struct LittleKeypressDemo: App {
  var body: some Scene {
    Window("Keypress Demo", id: "keypress-demo") {
      ContentView()
    }
    .windowStyle(.hiddenTitleBar)
    .windowResizability(.contentSize)
    .windowBackgroundDragBehavior(.enabled)
  }
}

struct ContentView: View {
  var body: some View {
    VStack {
      KeyPressButton(
        icon: "arrowtriangle.right.fill",
        keyCode: 124,
        eventFlags: [.maskSecondaryFn, .maskControl]
      )
      KeyPressButton(
        label: "C",
        keyCode: 8,
        eventFlags: [.maskSecondaryFn, .maskControl]
      )
      KeyPressButton(
        icon: "arrow.down.to.line",
        keyCode: 119,
        eventFlags: [ /* .maskSecondaryFn, */ .maskControl]
      )
    }
    .padding()
  }
}

struct KeyPressButton: View {
  let icon: String?
  let label: String?
  let keyCode: CGKeyCode
  let eventFlags: CGEventFlags

  init(
    icon: String? = nil,
    label: String? = nil,
    keyCode: CGKeyCode,
    eventFlags: CGEventFlags
  ) {
    self.icon = icon
    self.label = label
    self.keyCode = keyCode
    self.eventFlags = eventFlags
  }

  var body: some View {
    Button(action: {
      simulateKeyPress(keyCode: keyCode, eventFlags: eventFlags)
    }) {
      HStack {
        if eventFlags.contains(.maskSecondaryFn) {
          Image(systemName: "globe")
        }
        if eventFlags.contains(.maskControl) {
          Image(systemName: "control")
        }
        if let icon = icon {
          Image(systemName: icon)
        } else if let label = label {
          Text(label)
        }
      }
    }
    .controlSize(.large)
  }
}

func simulateKeyPress(keyCode: CGKeyCode, eventFlags: CGEventFlags) {
  guard let eventDown = CGEvent(
    keyboardEventSource: nil,
    virtualKey: keyCode,
    keyDown: true
  ),
    let eventUp = CGEvent(
      keyboardEventSource: nil,
      virtualKey: keyCode,
      keyDown: false
    ) else {
    print("Error creating CGEvent for keyCode \(keyCode)")
    return
  }
  eventDown.flags = eventFlags
  eventUp.flags = eventFlags
  eventDown.post(tap: .cghidEventTap)
  eventUp.post(tap: .cghidEventTap)
}

All I need is a nudge in the right direction.

This problem is a showstopper for the project I’m currently working on.

I think I’ve found where the problem is.

It looks like .maskSecondaryFn in CGEventFlags corresponds to 0xFF0100000030, but the usage ID from IOHID for real hardware Fn (Globe) key is 0xFF00000003. This mismatch is likely causing the problem during key simulation.

The question now is: how can this lower-level hex code be used for the purpose of simulating key presses?

Apple’s own macOS onscreen Keyboard Viewer also suffers from the same problem, where simulating the combination of Fn + Control + directional keys behaves incorrectly, further confirming this as a bug in the event handling system.

Steps to Reproduce:

  1. Open the macOS onscreen Keyboard Viewer.
  2. Press the combination Fn (Globe) + Control + C and observe the correct window management behavior (the key window beneath will position to the center). The appropriate menu item WindowCenter will flash and highlight.
  3. Now try to simulate the key combo Fn + Control + Right Arrow (corresponding to the menu item WindowMove & ResizeRight). The expected behavior is that the key window should move and resize to the right half of the screen, but it doesn’t.

Filed under FB15532267

Simulating Fn (Globe) + Arrow Key Events Ignoring Fn Modifier
 
 
Q