<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: QUICOPY</title>
    <description>The latest articles on DEV Community by QUICOPY (@quicopy).</description>
    <link>https://dev.to/quicopy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3886173%2Fcecd83d7-aff8-4844-9e98-68ea115f1a29.png</url>
      <title>DEV Community: QUICOPY</title>
      <link>https://dev.to/quicopy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/quicopy"/>
    <language>en</language>
    <item>
      <title>Shipping Global Keyboard Shortcuts on macOS Sandbox: The Part Apple Doesn't Document</title>
      <dc:creator>QUICOPY</dc:creator>
      <pubDate>Sat, 18 Apr 2026 14:49:56 +0000</pubDate>
      <link>https://dev.to/quicopy/shipping-global-keyboard-shortcuts-on-macos-sandbox-the-part-apple-doesnt-document-57no</link>
      <guid>https://dev.to/quicopy/shipping-global-keyboard-shortcuts-on-macos-sandbox-the-part-apple-doesnt-document-57no</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I shipped a macOS menu bar app (&lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt;) to the Mac App Store. Its core feature is &lt;strong&gt;global keyboard shortcuts that output text in any app&lt;/strong&gt;. The obvious implementation — &lt;code&gt;CGEvent.post()&lt;/code&gt; — is silently blocked by App Sandbox. Apple does not document this clearly. Here's what actually works, and why.&lt;/p&gt;

&lt;p&gt;If you're building anything that simulates keyboard input on macOS and targeting the App Store, this will save you a week.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I wanted a menu bar app where pressing &lt;code&gt;⌘⇧1&lt;/code&gt; in any application types a piece of pre-set text at the cursor. TextExpander-style. Simple, right?&lt;/p&gt;

&lt;p&gt;Two subproblems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; a global keyboard shortcut (even when my app is not focused)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; text into whatever app is focused&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both sound like 10-line solutions. Neither is, if you want to be on the Mac App Store.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capturing Global Shortcuts: CGEvent Tap
&lt;/h2&gt;

&lt;p&gt;This part is mostly fine. Use &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; with &lt;code&gt;.cgSessionEventTap&lt;/code&gt; and listen for &lt;code&gt;.keyDown&lt;/code&gt; events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;eventMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kt"&gt;CGEventType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyDown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tapCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgSessionEventTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headInsertEventTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;eventsOfInterest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEventMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventMask&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// Inspect event.flags and keycode here&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Unmanaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;passUnretained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sandbox gotcha:&lt;/strong&gt; &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; requires the user to grant your app &lt;strong&gt;Input Monitoring&lt;/strong&gt; (or in some cases Accessibility) permission. This is fine — you get a permission prompt, user approves, done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;This part works in a sandboxed Mac App Store app.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; monitor events at the session level as long as the user grants the permission. The permission is called "Input Monitoring" in macOS 10.15+ and it specifically covers event taps.&lt;/p&gt;

&lt;p&gt;No entitlement file magic needed for input monitoring — the sandbox allows the system permission prompt to handle it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outputting Text: The Approach That Everyone Tries First
&lt;/h2&gt;

&lt;p&gt;Naturally, you reach for &lt;code&gt;CGEvent.post()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Synthesize ⌘V&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stateID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combinedSessionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cmdVDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;keyboardEventSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;virtualKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;keyDown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cmdVDown&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskCommand&lt;/span&gt;
&lt;span class="n"&gt;cmdVDown&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgSessionEventTap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works flawlessly in a non-sandboxed development build. You celebrate.&lt;/p&gt;

&lt;p&gt;Then you enable App Sandbox (required for App Store), run it, and... &lt;strong&gt;nothing happens&lt;/strong&gt;. No error. No log. No permission prompt. The &lt;code&gt;.post()&lt;/code&gt; call just silently does nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Undocumented Rule
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CGEvent.post()&lt;/code&gt; is completely blocked inside App Sandbox.&lt;/strong&gt; There is no entitlement that re-enables it. Apple will not provide one.&lt;/p&gt;

&lt;p&gt;I spent two days reading every developer forum thread, every entitlement documentation page, every StackOverflow answer. The closest Apple documentation admits it is this line in the App Sandbox Design Guide, which you have to squint to find:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sending synthetic events to other processes is disallowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. One sentence. In a guide that most indie devs stop reading after the "Container Directory" section.&lt;/p&gt;

&lt;p&gt;The workarounds I saw suggested online, which &lt;strong&gt;do not work&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Adding &lt;code&gt;com.apple.security.temporary-exception.user-interaction&lt;/code&gt; — deprecated, no longer honored&lt;/li&gt;
&lt;li&gt;❌ Adding &lt;code&gt;com.apple.security.device.input&lt;/code&gt; — wrong entitlement, not related&lt;/li&gt;
&lt;li&gt;❌ Using &lt;code&gt;HIDPostAuxKey&lt;/code&gt; — same sandbox rule applies&lt;/li&gt;
&lt;li&gt;❌ Using &lt;code&gt;IOHIDPostEvent&lt;/code&gt; — same&lt;/li&gt;
&lt;li&gt;❌ Requesting Accessibility permission — has no effect on this&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Actually Works: AppleScript System Events
&lt;/h2&gt;

&lt;p&gt;The workaround is to pretend to be an automation client and ask &lt;code&gt;System Events&lt;/code&gt; (an Apple-signed helper) to type for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the text into the clipboard (&lt;code&gt;NSPasteboard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Send an AppleScript "System Events → keystroke ⌘V" via &lt;code&gt;NSAppleScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restore the previous clipboard contents
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;outputText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;savedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pasteboardItems&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* snapshot */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
    tell application "&lt;/span&gt;&lt;span class="kt"&gt;System&lt;/span&gt; &lt;span class="kt"&gt;Events&lt;/span&gt;&lt;span class="s"&gt;"
        keystroke "&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="s"&gt;" using command down
    end tell
    """&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSDictionary&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kt"&gt;NSAppleScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeAndReturnError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Restore clipboard after a short delay (let the paste complete)&lt;/span&gt;
    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// re-write savedItems&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Required entitlement:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.automation.apple-events&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Required Info.plist entry&lt;/strong&gt; (macOS 10.14+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAppleEventsUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;QUICOPY uses System Events to paste your shortcut text into the currently focused application.&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first use, macOS shows the user a permission prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"QUICOPY wants permission to control System Events."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;User clicks Allow → done. Works for all subsequent invocations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Latency Trade-off
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CGEvent.post()&lt;/code&gt; (if it worked) would be ~5 ms. The AppleScript round-trip is 40–80 ms on a modern M-series Mac, closer to 100 ms on Intel.&lt;/p&gt;

&lt;p&gt;For a text-expansion use case, this is invisible to the user — it feels instant. The whole flow (user releases shortcut → text appears) stays under 100 ms on Apple Silicon, which is the human perception threshold.&lt;/p&gt;

&lt;p&gt;If you need &lt;em&gt;true&lt;/em&gt; sub-10-ms latency (game input, accessibility apps), App Store distribution is probably not your path. Direct notarized distribution outside the store lifts the sandbox and lets you use &lt;code&gt;CGEvent.post()&lt;/code&gt; directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Clipboard Restoration Problem
&lt;/h2&gt;

&lt;p&gt;The paste trick mutates the user's clipboard. If you don't restore it, users will paste your shortcut text into their next real Cmd+V — very bad UX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naive fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ... do paste ...&lt;/span&gt;
&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; clipboard can hold multiple representations — images, rich text, file promises. A &lt;code&gt;.string(forType:)&lt;/code&gt; snapshot throws all of that away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pasteboardItems&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboardItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboardItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... paste ...&lt;/span&gt;

&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Iterate every &lt;code&gt;NSPasteboardItem.types&lt;/code&gt; and copy the raw &lt;code&gt;Data&lt;/code&gt; for each. Preserves everything including image clipboard, file references, and rich text.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Timing Problem
&lt;/h2&gt;

&lt;p&gt;If you restore the clipboard too fast, you restore &lt;em&gt;before&lt;/em&gt; &lt;code&gt;System Events&lt;/code&gt; has read it → the paste gets nothing.&lt;/p&gt;

&lt;p&gt;If you restore too slowly, there's a visible window where the user's original clipboard is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empirical finding:&lt;/strong&gt; 100 ms delay is safe on Apple Silicon. 150 ms on Intel. &lt;code&gt;executeAndReturnError&lt;/code&gt; does not block until the paste is truly complete — it returns as soon as the AppleScript dispatch succeeds.&lt;/p&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; rely on &lt;code&gt;DispatchQueue.sync&lt;/code&gt; thinking it'll wait — it won't, the paste is asynchronous on System Events' side.&lt;/p&gt;




&lt;h2&gt;
  
  
  App Store Review Pitfalls
&lt;/h2&gt;

&lt;p&gt;When I submitted to the App Store, the reviewer flagged &lt;strong&gt;Accessibility&lt;/strong&gt; in my initial build. I had included Accessibility API calls as a fallback for when Automation permission wasn't granted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple's position&lt;/strong&gt;: If you can accomplish the task through a less-privileged mechanism (Automation), don't ask for the more-privileged one (Accessibility).&lt;/p&gt;

&lt;p&gt;I removed all Accessibility code and replaced it with a non-blocking permission-request flow that only asks for Automation. Resubmitted. Approved.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Lesson:&lt;/strong&gt; Minimize entitlements. Each entitlement is a question the reviewer will ask "do you really need this?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What the Final Architecture Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User presses ⌘⇧1
         │
         ▼
CGEvent Tap (captures system-wide keydown)
         │
         ▼
Match against user's shortcut mapping
         │
         ▼
Load text snippet → NSPasteboard
         │
         ▼
NSAppleScript: "keystroke v using command down"
         │
         ▼
macOS System Events types Cmd+V into focused app
         │
         ▼
Restore previous clipboard (100ms delay)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total latency: ~80–100 ms on M-series.&lt;br&gt;
Total code: ~200 lines Swift.&lt;br&gt;
App binary size: 2.8 MB (no Electron).&lt;br&gt;
App Store: approved.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You're Building Something Similar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the App Sandbox Design Guide twice.&lt;/strong&gt; The fine print matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume CGEvent.post works&lt;/strong&gt;, even if your dev build works. Test with sandbox enabled early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot clipboard as pasteboard items&lt;/strong&gt;, not strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a permission request UI&lt;/strong&gt; for &lt;code&gt;com.apple.security.automation.apple-events&lt;/code&gt;. Without it, the AppleScript fails silently the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize entitlements.&lt;/strong&gt; Every one is a review friction point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider whether you really need App Store&lt;/strong&gt;. Direct notarized distribution gives you more freedom but less reach.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About QUICOPY
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt; — a menu bar app that does exactly what this post describes, plus 7 built-in AI prompt templates mapped to &lt;code&gt;⌘⇧1&lt;/code&gt; through &lt;code&gt;⌘⇧7&lt;/code&gt;. It's on the &lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;Mac App Store&lt;/a&gt; for $9.99 lifetime or $1.99/month.&lt;/p&gt;

&lt;p&gt;If you want to see the end result of all this sandbox wrestling, there it is.&lt;/p&gt;

&lt;p&gt;Questions or corrections? Happy to discuss in the comments — especially interested in hearing from anyone who's found a way to make &lt;code&gt;CGEvent.post()&lt;/code&gt; work under sandbox (I don't think it's possible, but would love to be wrong).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;quicopy.com&lt;/a&gt;. Follow me for more macOS indie dev notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>appstore</category>
      <category>sandbox</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
