<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>NSEvent &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/nsevent/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Wed, 10 Jan 2024 21:15:31 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>NSEvent &#8211; Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>Mac app sandboxing interferes with drag &#038; drop</title>
		<link>https://wadetregaskis.com/mac-app-sandboxing-interferes-with-drag-drop/</link>
					<comments>https://wadetregaskis.com/mac-app-sandboxing-interferes-with-drag-drop/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 06 Jan 2024 02:06:10 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[App sandboxing]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[Insecure]]></category>
		<category><![CDATA[NSEvent]]></category>
		<category><![CDATA[NSPasteboard]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7375</guid>

					<description><![CDATA[Failed to get a sandbox extension Right from there, you know you&#8217;re going to have a bad day. 😔 Then you try to actually use the file dropped on your app, and you get: Upload preparation for claim 1C0F9013-4DEB-4E5D-8896-F522AA979BA6 completed with error: Error Domain=NSCocoaErrorDomain Code=513 "“Example.jpg” couldn’t be copied because you don’t have permission to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/mac-app-sandboxing-interferes-with-drag-drop/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">Failed to get a sandbox extension</pre>



<p>Right from there, you know you&#8217;re going to have a bad day. 😔</p>



<p>Then you try to actually use the file dropped on your app, and you get:</p>



<pre class="wp-block-preformatted">Upload preparation for claim 1C0F9013-4DEB-4E5D-8896-F522AA979BA6 completed with error: Error Domain=NSCocoaErrorDomain Code=513 "“Example.jpg” couldn’t be copied because you don’t have permission to access “CoordinatedZipFilejxc2lC”." UserInfo={NSSourceFilePathErrorKey=/Users/SadPanda/Pictures/Example.jpg, NSUserStringVariant=(
    Copy
), NSDestinationFilePath=/Users/SadPanda/Library/Containers/com.me.MyApp/Data/tmp/CoordinatedZipFilejxc2lC/Example.jpg, NSFilePath=/Users/SadPanda/Pictures/Example.jpg, NSUnderlyingError=0x600000ad0cf0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}</pre>



<p>There&#8217;s a variety of ways to run afoul of this.  Here&#8217;s a particular one, for illustration.</p>



<h2 class="wp-block-heading">Noticing when drag &amp; drop operations <em>start</em></h2>



<p>Say you want to have a drop zone in your app that draws attention to itself whenever the user starts dragging a relevant file.  Merely intelligent, courteous UI.</p>



<p>There&#8217;s no built-in facility for this &#8211; both AppKit and SwiftUI drag &amp; drop APIs only start functioning once the dragged item enters the drop zone itself (the view, or at best window, that you&#8217;ve made a drop receiver).</p>



<p>There <em>is</em> a convention on how to work around this &#8211; you can <em>deduce</em> that a drag is occurring by watching mouse events and the drag pasteboard.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<h3 class="wp-block-heading">Drag &amp; drop implementation detail</h3>



<p>macOS uses <em>pasteboards</em> to share data between applications (represented in AppKit by <a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a>), not just for copy &amp; paste as you&#8217;re probably already familiar, but for drag &amp; drop as well, among several other things.  You can also create your own custom pasteboards, namespaced to be completely independent of the &#8216;built-in&#8217; ones, for your own purposes.</p>



<h3 class="wp-block-heading">Pasteboard implementation detail</h3>



<p>The standard system pasteboards are <em>always</em> accessible to your application, even when they&#8217;re nominally not relevant (such as when no drag is actually occurring).  In fact the contents of the last drag are left indefinitely on the pasteboard (by default, unless an app explicitly clears it).  Which is an annoyance as it complicates the following…</p>
</div></div>



<h3 class="wp-block-heading">Deducing what&#8217;s going on by spying on global mouse events</h3>



<p>When you see a drag event you don&#8217;t immediately know anything more than that the mouse moved with the left mouse button held down.  That can mean anything &#8211; the user might be pulling out a selection rectangle, or drawing in a graphics application, or just randomly dragging the mouse around.</p>



<p>Since the contents of the drag pasteboard are left there indefinitely after a drag concludes, you can&#8217;t just use the existence of something on the drag pasteboard as an indication that a drag is in progress.</p>



<p>However, pasteboards have a &#8220;change count&#8221;.  What this is actually counting is ill-defined and seemingly not about the actual <em>contents</em> of the pasteboard, but in a nutshell it can be used to mean essentially that &#8211; the count might change even if the contents don&#8217;t change, but it seems it will <em>always</em> change if the contents do change.  So, some false positives, but no false negatives, which is the important thing.</p>



<p>So, when you see a mouse drag event, you can look at the drag pasteboard&#8217;s change count and see if it changed since the last mouse drag event.  If it did, that&#8217;s a pretty strong suggestion &#8211; although admittedly not a guarantee &#8211; that the user is performing a drag &amp; drop operation.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>There is a race condition here.  Mouse events are handled by the WindowServer which runs at <em>very</em> high priority and completely asynchronous to app activity like loading up the pasteboard with dragged items &#8211; and it does actually take some time for an app like the Finder to populate the pasteboard when the drag is initiated.  Only tens of milliseconds, typically, but that&#8217;s an eon in computer terms.</p>



<p>So you might observe the key events &#8211; the pasteboard changing and the first mouse down of a drag &#8211; in any order.</p>



<p>Fortunately, it&#8217;s rare to lose the race in a way that matters, because you&#8217;ll get a mouse dragged event virtually every time the mouse moves, even if just by a single pixel.  So even if you get the first mouse dragged event before the source app has actually populated the pasteboard &#8211; which is in fact common, in my experience &#8211; you&#8217;ll invariably get a bunch more practically immediately as the user continues the drag.  Sooner or later the pasteboard will be updated, so you&#8217;ll eventually notice.  Technically you&#8217;re late in recognising that a drag &amp; drop operation has started, but in general the delay is imperceptible.</p>
</div></div>



<p>The essential code is:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> dragPasteboard = </span><span style="color: #795E26">NSPasteboard</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: .</span><span style="color: #001080">drag</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> lastDragPasteboardChangeCount: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = dragPasteboard.</span><span style="color: #001080">changeCount</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> mouseDragWatcher = NSEvent.</span><span style="color: #795E26">addGlobalMonitorForEvents</span><span style="color: #000000">(</span><span style="color: #795E26">matching</span><span style="color: #000000">: [.</span><span style="color: #001080">leftMouseDragged</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                                         </span><span style="color: #795E26">handler</span><span style="color: #000000">: { event </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> currentChangeCount = dragPasteboard.</span><span style="color: #001080">changeCount</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> lastDragPasteboardChangeCount != currentChangeCount {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// The user very likely just started dragging something.</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    lastDragPasteboardChangeCount = currentChangeCount</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> mouseUpWatcher = NSEvent.</span><span style="color: #795E26">addGlobalMonitorForEvents</span><span style="color: #000000">(</span><span style="color: #795E26">matching</span><span style="color: #000000">: [.</span><span style="color: #001080">leftMouseUp</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                                       </span><span style="color: #795E26">handler</span><span style="color: #000000">: { event </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// The drag ended.</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Note that I&#8217;ve omitted various ancillary details, such as avoiding strong retains of <code>self</code> (where applicable), error handling (<code><a href="https://developer.apple.com/documentation/appkit/nsevent/1535472-addglobalmonitorforevents" data-wpel-link="external" target="_blank" rel="external noopener">addGlobalMonitorForEvents</a></code> can return nil), removing the monitors when you&#8217;re done with them, etc.</p>



<p>Also, <code><a href="https://developer.apple.com/documentation/appkit/nsevent" data-wpel-link="external" target="_blank" rel="external noopener">NSEvent</a></code>&#8216;s monitoring implicitly relies on <code><a href="https://developer.apple.com/documentation/foundation/runloop" data-wpel-link="external" target="_blank" rel="external noopener">RunLoop</a></code>, so actually using the above &#8211; and <em>actually</em> getting your handler called &#8211; is predicated on having a suitable runloop going (in my experience it has to be the <em>main</em> runloop, but maybe it&#8217;s possible to use a different runloop in the right <a href="https://developer.apple.com/documentation/foundation/runloop/mode" data-wpel-link="external" target="_blank" rel="external noopener">mode</a> &#8211; presumably <code><a href="https://developer.apple.com/documentation/foundation/runloop/mode/1428765-eventtracking" data-wpel-link="external" target="_blank" rel="external noopener">eventTracking</a></code>).  In a typical GUI application that&#8217;s all set up for you automagically, but in other cases you have to do it yourself &#8211; refer to <a href="https://stackoverflow.com/questions/25496336/addglobalmonitorforeventsmatchingmask-not-working" data-wpel-link="external" target="_blank" rel="external noopener">this</a> for more details and suggestions.</p>
</div></div>



<p>And the above code works fine as presented.  The problem arises if &amp; when you start actually looking at the pasteboard during the drag…</p>



<h2 class="wp-block-heading">You get only one look at the dragged files</h2>



<p>Files, when dragged, are represented mainly as their paths (technically, <code><a href="https://developer.apple.com/documentation/foundation/nsurl" data-wpel-link="external" target="_blank" rel="external noopener">NSURL</a></code>s).  That&#8217;s the &#8220;public.file-url&#8221; UTI (and the <a href="https://developer.apple.com/documentation/appkit/nspasteboard/pasteboardtype/2919747-fileurl" data-wpel-link="external" target="_blank" rel="external noopener">Apple URL pasteboard type</a> for backwards compatibility).  Though you&#8217;ll also see a bunch of other types reported if you ask the pasteboard what it&#8217;s carrying, e.g.:</p>



<pre class="wp-block-preformatted">Apple URL pasteboard type
CorePasteboardFlavorType 0x6675726C
NSFilenamesPboardType
com.apple.finder.node
dyn.ah62d4rv4gu8y6y4grf0gn5xbrzw1gydcr7u1e3cytf2gn
dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu
public.file-url</pre>



<p>Merely inspecting the UTIs in the pasteboard is fine &#8211; that doesn&#8217;t interfere with anything.  So if all you care about is if <em>any</em> kind of file (or folder) is being dragged, you&#8217;re set.  But if you want to only react to <em>some</em> types of files or folders, you need to know more.</p>



<p>If you ask for the URL &#8211; even without actually <em>using</em> it &#8211; you trigger some behind the scenes activity involving app sandboxing.  This prevents the file being made accessible to your app if &amp; when it actually is dropped into your app.</p>



<p><em>When things are working correctly</em>, when a file is dragged and dropped onto a receptive view in your app a link to that file is created inside your own app&#8217;s container.  It&#8217;s that <em>link</em> that you actually have access to &#8211; the original file cannot be accessed directly.  That link seems to persist for a while &#8211; perhaps until your app is quit &#8211; so once you have it you&#8217;re set.</p>



<p>I don&#8217;t know why merely peeking at the file path (URL) prevents this link being created, but it does.</p>



<p>Sigh.</p>



<p>FB13520048.</p>



<h3 class="wp-block-heading">Failed workarounds</h3>



<p>Sadly the <em>only</em> meaningful existing documentation of this problem that Bing or Google can find is <a href="https://stackoverflow.com/questions/67295419/got-an-error-when-dragging-files-using-nsevent-macos" data-wpel-link="external" target="_blank" rel="external noopener">this one StackOverflow question</a>.  Which is unresolved &#8211; the claimed hacks &amp; workarounds don&#8217;t actually work, at least not anymore.</p>



<p><a href="https://stackoverflow.com/questions/67295419/got-an-error-when-dragging-files-using-nsevent-macos/67295974#67295974" data-wpel-link="external" target="_blank" rel="external noopener">Supposedly</a> on some older versions of macOS you could <a href="https://developer.apple.com/documentation/foundation/nsurl/1417795-bookmarkdata" data-wpel-link="external" target="_blank" rel="external noopener">create a bookmark of the file</a> (presumably a <a href="https://developer.apple.com/documentation/foundation/nsurl/bookmarkcreationoptions/1413824-withsecurityscope" data-wpel-link="external" target="_blank" rel="external noopener">security-scoped</a> one) and that would implicitly secure your access to it.  But that seems pretty clearly to no longer work &#8211; assuming it ever did &#8211; because you can&#8217;t create a bookmark for files you can&#8217;t read, and until the drop occurs you don&#8217;t have read access to the file.</p>



<p>Similarly, you can <em>supposedly</em> use <code><a href="https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso" data-wpel-link="external" target="_blank" rel="external noopener">startAccessingSecurityScopedResource</a></code> to indefinitely lock in your access to the file, but this doesn&#8217;t apply if you don&#8217;t have access to it to begin with.  So, again, not useful here.</p>



<h3 class="wp-block-heading">Partial workaround</h3>



<p>While I knew that using <code>canReadObject(forClasses: [NSImage.self])</code> ran afoul of this, I happened to notice there&#8217;s a logically equivalent method on <code>NSImage</code> itself &#8211; <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1520039-caninit" data-type="link" data-id="https://developer.apple.com/documentation/appkit/nsimage/1520039-caninit" data-wpel-link="external" target="_blank" rel="external noopener">NSImage.canInit(with:)</a></code> &#8211; which I tried, and lo and behold it works!  No sandboxing issues.</p>



<p>I disassembled it (thanks <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a>!) and saw that it&#8217;s basically just doing some UTI pre-checks &#8211; already known to be safe &#8211; to see if there&#8217;s a file URL in the pasteboard, and then it fetches that file URL using <code>readObjects(forClasses: [NSURL.self])</code>, the same method that if you or I call results in this bug surfacing.  The difference is it uses a secret <a href="https://developer.apple.com/documentation/appkit/nspasteboard/readingoptionkey" data-wpel-link="external" target="_blank" rel="external noopener">reading option key</a> &#8211; &#8220;NSPasteboardURLReadingSecurityScopedFileURLsKey&#8221;!  Apparently that&#8217;s the magic sauce which allows you to read the URL <em>without</em> triggering the bug.  You still can&#8217;t actually <em>access</em> the file, but at least you can e.g. examine its file extension (which is essentially what <code>NSImage.canInit(with:)</code> does).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> URLs = dragPasteboard.</span><span style="color: #795E26">readObjects</span><span style="color: #000000">(</span><span style="color: #795E26">forClasses</span><span style="color: #000000">: [NSURL.self],</span></span>
<span class="line"><span style="color: #000000">                                         </span><span style="color: #795E26">options</span><span style="color: #000000">: [NSPasteboard.</span><span style="color: #795E26">ReadingOptionKey</span><span style="color: #000000">(</span><span style="color: #795E26">rawValue</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;NSPasteboardURLReadingSecurityScopedFileURLsKey&quot;</span><span style="color: #000000">) : kCFBooleanTrue]) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">for</span><span style="color: #000000"> u </span><span style="color: #AF00DB">in</span><span style="color: #000000"> URLs {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Go nuts!</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Incidentally, I noticed it also calls <code>startAccessingSecurityScopedResource</code> but in my testing it always gets a failure response from it too.  So that apparently is irrelevant.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h2 class="wp-block-heading">Addendum: Michael Tsai&#8217;s Blog</h2>



<p>I was reading through articles in <a href="https://netnewswire.com" data-wpel-link="external" target="_blank" rel="external noopener">NetNewsWire</a>, including a few of the latest from <a href="https://mjtsai.com" data-wpel-link="external" target="_blank" rel="external noopener">Michael Tsai</a>, and for one in particular I thought, &#8220;this sounds <em>really</em> familiar for some reason&#8221;.  Hah, because <a href="https://mjtsai.com/blog/2024/01/10/mac-app-sandboxing-interferes-with-drag-drop/" data-wpel-link="external" target="_blank" rel="external noopener">he was quoting part of this post</a>.</p>



<p>I&#8217;m very flattered to be linked to by Michael &#8211; I have no idea how he found my obscure website to begin with &#8211; as his blog is one of my favourite Apple developer news / discussion feeds.  He does such a great job of collecting valuable links and collating them together on timely and/or valuable topics.  His own commentary &#8211; if he adds any &#8211; is concise and valuable.  If you&#8217;re not following <a href="https://mjtsai.com/blog/" data-wpel-link="external" target="_blank" rel="external noopener">his blog</a>, you should rectify that. 🙂</p>



<p>I also now wish I&#8217;d written this post better. 😝</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/mac-app-sandboxing-interferes-with-drag-drop/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7375</post-id>	</item>
	</channel>
</rss>
