admin管理员组

文章数量:1387356

I have a Mac menubar app that tries to show/hide the Emoji picker (Character palette) in the frontmost app via a menu item.

The best way I found to do this was to trigger the Emoji picker's system keyboard shortcut — fn-E (Globe-E) — via simulated key press sent to the frontmost app:

pid_t psn = NSWorkspace.sharedWorkspace.frontmostApplication.processIdentifier;

CGEventRef keyup, keydown;
CGEventFlags flags = kCGEventFlagMaskSecondaryFn;

keydown = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)kVK_ANSI_E, true);
CGEventSetFlags(keydown, flags);

keyup = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)kVK_ANSI_E, false);
CGEventSetFlags(keyup, flags);

CGEventPostToPid(psn, keydown);
CGEventPostToPid(psn, keyup);

CFRelease(keydown);
CFRelease(keyup);

This no longer works in macOS 15 Sequoia. The code above simply fails without error.

I found one similar question on StackOverflow, with a suggestion from @Rudolfs-Bundulis to set timestamps on the events before posting them, e.g. add these lines:

CGEventSetTimestamp(keydown, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));
CGEventSetTimestamp(keyup, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));

This still doesn't work. I also tried creating an NSEvent key event with a timestamp and windowId, but that also doesn't work. Am I doing something wrong with the timestamps, or is there something else I'm missing?

I have a Mac menubar app that tries to show/hide the Emoji picker (Character palette) in the frontmost app via a menu item.

The best way I found to do this was to trigger the Emoji picker's system keyboard shortcut — fn-E (Globe-E) — via simulated key press sent to the frontmost app:

pid_t psn = NSWorkspace.sharedWorkspace.frontmostApplication.processIdentifier;

CGEventRef keyup, keydown;
CGEventFlags flags = kCGEventFlagMaskSecondaryFn;

keydown = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)kVK_ANSI_E, true);
CGEventSetFlags(keydown, flags);

keyup = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)kVK_ANSI_E, false);
CGEventSetFlags(keyup, flags);

CGEventPostToPid(psn, keydown);
CGEventPostToPid(psn, keyup);

CFRelease(keydown);
CFRelease(keyup);

This no longer works in macOS 15 Sequoia. The code above simply fails without error.

I found one similar question on StackOverflow, with a suggestion from @Rudolfs-Bundulis to set timestamps on the events before posting them, e.g. add these lines:

CGEventSetTimestamp(keydown, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));
CGEventSetTimestamp(keyup, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));

This still doesn't work. I also tried creating an NSEvent key event with a timestamp and windowId, but that also doesn't work. Am I doing something wrong with the timestamps, or is there something else I'm missing?

Share Improve this question edited Mar 21 at 22:02 HangarRash 15.1k5 gold badges20 silver badges55 bronze badges asked Mar 18 at 17:45 AdamAdam 5,17522 silver badges30 bronze badges 6
  • 2 I tried your code and it works for me. Does your app have permission in the System Prefs and did you remove and add your app again? Does the frontmost app have a "Emoji & Symbols" menu item with Fn-E? – Willeke Commented Mar 19 at 6:02
  • Off topic: Calling the pid variable psn is confusing. – Willeke Commented Mar 19 at 6:04
  • Yes there are several things that might be off: 1) is the receiving window focused (you are taking the frontmost window, but just outlining that out of focus windows will not receive the event) 2) having the Accessibility (not sure about Automation) permissions enabled for the app, you can actually verify the Accessibility permissions from within the app via AXIsProcessTrusted 3) Try sending the event via CGEventPostToPSN (and using the serial number) instead of pid and CGEventPostToPid – Rudolfs Bundulis Commented Mar 19 at 8:20
  • @RudolfsBundulis 1) The event is sent to the frontmost app (and its menus), not a window. 3) CGEventPostToPSN is deprecated in CGEvent.h. How do you get a psn nowadays?. – Willeke Commented Mar 19 at 11:53
  • @Willeke GetProcessForPID, and yeah they are deprecated, just mentioned those since they seem to work for me. – Rudolfs Bundulis Commented Mar 19 at 19:23
 |  Show 1 more comment

1 Answer 1

Reset to default 0

Okay, with a little help I have figured out what was going on. There were two issues:

First, I needed to reset permissions. That is, I had to go to System Settings > Privacy & Security > Accessibility, remove my app, then add it back again (toggling the switch was insufficient). In my app, calling AXIsProcessTrusted() returned true both before and after the reset. This seems to me to be a macOS bug.

Second, I was not accounting for how fn+e works as compared to many other hotkeys. Many hotkeys are remapped when you switch keyboard layouts, but fn+e is not, meaning I need to send the key code for the current layout's E key. kVK_ANSI_E is the virtual key code that represents the ANSI keyboard layout's E key.

So the solution was to figure out which CGKeyCode corresponds to the current keyboard layout's E key and use that instead of hard-coding kVK_ANSI_E. I used the Sauce library to do this.

本文标签: objective cPosting keypress CGEvent fails in macOS 15 SequoiaStack Overflow