Skip to content

Faster frontmost app detection on macOS via NSWorkspace + CGEvent#127

Merged
maraf merged 3 commits intomainfrom
maraf/upgraded-tribble
Apr 22, 2026
Merged

Faster frontmost app detection on macOS via NSWorkspace + CGEvent#127
maraf merged 3 commits intomainfrom
maraf/upgraded-tribble

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented Apr 22, 2026

Every hotkey press previously spawned one or more osascript processes to read the frontmost app, reactivate the previous app, and send Cmd+V. Each AppleScript invocation fork/execs a shell and spins up the AppleScript runtime (tens to hundreds of milliseconds), which is noticeable on the hot path from hotkey to paste.

This PR replaces all three AppleScript calls with direct in-process macOS APIs:

  • GetFrontmostApplication now calls [NSWorkspace sharedWorkspace].frontmostApplication through the Objective-C runtime (objc_msgSend), reading processIdentifier, localizedName, and bundleIdentifier directly. AppKit is dlopened defensively on first use so the call works regardless of initialization order (it is a no-op when Avalonia has already linked it).
  • ActivateProcess uses NSRunningApplication.runningApplicationWithProcessIdentifier: + activateWithOptions: with NSApplicationActivateIgnoringOtherApps.
  • SendPasteShortcut posts a synthetic Cmd+V via CGEventCreateKeyboardEvent and CGEventPost, with CFRelease on all Create-rule objects.

The now-unused RunAppleScript helper is removed.

Notes for reviewers

  • Classic DllImport is used rather than LibraryImport to avoid enabling AllowUnsafeBlocks project-wide (the source generator for LibraryImport requires it).
  • CGEventPost needs the same Accessibility/Input Monitoring permission the AppleScript keystroke path already required, so users with existing permissions are unaffected. NSRunningApplication.activateWithOptions: actually relaxes one requirement: the old path also needed System Events automation permission, which is no longer used.
  • Smoke-tested the NSWorkspace lookup against a live process and confirmed it returns the correct pid/name/bundle. Build is green and all 110 tests pass.

maraf and others added 2 commits April 22, 2026 12:18
Replace the osascript-based 'frontmost' query with a direct call to
[NSWorkspace sharedWorkspace].frontmostApplication via the Objective-C
runtime. Spawning osascript took tens to hundreds of milliseconds on
every hotkey press; the in-process message send is effectively free.

Activation and paste keystroke still go through AppleScript and are
unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ActivateProcess now uses NSRunningApplication
runningApplicationWithProcessIdentifier: + activateWithOptions: via
objc_msgSend, and SendPasteShortcut posts Cmd+V via
CGEventCreateKeyboardEvent + CGEventPost. Both avoid spawning
osascript and run fully in-process.

The old RunAppleScript helper is no longer needed and is removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the AppleScript/osascript hot-path on macOS and replaces it with direct in-process AppKit/CoreGraphics calls for (1) detecting the frontmost application, (2) re-activating a target process, and (3) synthesizing Cmd+V.

Changes:

  • Replace frontmost-app detection with NSWorkspace.sharedWorkspace.frontmostApplication via objc_msgSend.
  • Replace process activation with NSRunningApplication.runningApplicationWithProcessIdentifier: + activateWithOptions:.
  • Replace AppleScript-based paste with CGEventCreateKeyboardEvent + CGEventPost, and remove the now-unused AppleScript runner.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Neptuo.Productivity.SnippetManager.Avalonia/MacOSApplication.cs Outdated
Address PR review feedback: surface a clearer error when dlopen cannot
load AppKit, and distinguish it from a plain NSWorkspace class lookup
miss. AppKitHandle is now read by the NSWorkspace-miss branch so the
field is genuinely used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@maraf maraf merged commit 983fa83 into main Apr 22, 2026
1 check passed
@maraf maraf deleted the maraf/upgraded-tribble branch April 22, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants