<?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>Apple &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/apple/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Thu, 02 Apr 2026 03:30:29 +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>Apple &#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>6k display comparison</title>
		<link>https://wadetregaskis.com/6k-display-comparison/</link>
					<comments>https://wadetregaskis.com/6k-display-comparison/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 07 Mar 2026 23:33:28 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[32U990A-S]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Dell]]></category>
		<category><![CDATA[LG]]></category>
		<category><![CDATA[PA32QCV]]></category>
		<category><![CDATA[Pro Display XDR]]></category>
		<category><![CDATA[U3224KB]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8747</guid>

					<description><![CDATA[It&#8217;s very clear that Apple were going for peak brightness above all else. Nobody else has even tried to make a bright 6k display &#8211; in fact, every non-Apple 6k display is outright dim by modern display standards &#8211; they&#8217;re barely brighter than the original 5k display in the 2015 iMac! For the price of&#8230; <a class="read-more-link" href="https://wadetregaskis.com/6k-display-comparison/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It&#8217;s very clear that Apple were going for peak brightness above all else. Nobody else has even tried to make a bright 6k display &#8211; in fact, every non-Apple 6k display is outright <em>dim</em> by modern display standards &#8211; they&#8217;re barely brighter than the original 5k display in the 2015 iMac!<sup data-fn="a041ec8b-df15-4e40-85ce-8259991341aa" class="fn"><a href="#a041ec8b-df15-4e40-85ce-8259991341aa" id="a041ec8b-df15-4e40-85ce-8259991341aa-link">1</a></sup></p>



<p>For the price of <em>one</em> Apple Pro Display XDR you can get <em>four</em> Asus ProArt 6k displays.</p>



<p>I suspect there&#8217;s only three 6k panel models in existence &#8211; the one used by Apple, the AUO one used by Asus<sup data-fn="08a8dafe-81b7-4c14-94ec-907400f178dc" class="fn"><a href="#08a8dafe-81b7-4c14-94ec-907400f178dc" id="08a8dafe-81b7-4c14-94ec-907400f178dc-link">2</a></sup>, and the LG one used by LG, Dell, &amp; Kuycon.</p>



<p>It&#8217;s strange to me that Dell haven&#8217;t dropped the price of their 6k display given that LG are offering the same panel in a much svelter package for 15% less (and you can get the very similar Asus display for 45% less!).</p>



<figure class="wp-block-table is-style-stripes"><table class="has-fixed-layout"><thead><tr><th></th><th class="has-text-align-center" data-align="center">Pro Display XDR</th><th class="has-text-align-center" data-align="center"><a href="https://www.lg.com/us/monitors/lg-32u990a-s-ultrafine-monitor" data-wpel-link="external" target="_blank" rel="external noopener">LG UltraFine™evo 6K Nano IPS Black Monitor with Thunderbolt™ 5 (32U990A-S)</a></th><th class="has-text-align-center" data-align="center"><a href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-6k-monitor-u3224kb/apd/210-bhbz/monitors-monitor-accessories" data-wpel-link="external" target="_blank" rel="external noopener">Dell UltraSharp 32 6K (U3224KB)</a></th><th class="has-text-align-center" data-align="center"><a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/" data-wpel-link="external" target="_blank" rel="external noopener">Asus ProArt Display 6K (PA32QCV)</a></th><th class="has-text-align-center" data-align="center"><a href="https://kuycon.us/monitors/G32P" data-wpel-link="external" target="_blank" rel="external noopener">Kuycon G32P</a></th></tr></thead><tbody><tr><td>Screen diagonal</td><td class="has-text-align-center" data-align="center">81 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td></tr><tr><td>Resolution</td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td></tr><tr><td>Pixel count</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td></tr><tr><td>Backlight zones</td><td class="has-text-align-center" data-align="center"><strong>576</strong></td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td></tr><tr><td>Pixels per backlight zone</td><td class="has-text-align-center" data-align="center"><strong>35,344</strong></td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center">21,233,664</td></tr><tr><td>Pixel density</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">224</td></tr><tr><td>Contrast ratio</td><td class="has-text-align-center" data-align="center"><strong>1,000,000 : 1</strong></td><td class="has-text-align-center" data-align="center">2,000 : 1 <sup data-fn="27847762-88f0-44ea-ba59-70adf9684471" class="fn"><a href="#27847762-88f0-44ea-ba59-70adf9684471" id="27847762-88f0-44ea-ba59-70adf9684471-link">3</a></sup></td><td class="has-text-align-center" data-align="center">2,000 : 1 <sup data-fn="6a31577c-b563-4fb9-9aca-74b093f68a7b" class="fn"><a href="#6a31577c-b563-4fb9-9aca-74b093f68a7b" id="6a31577c-b563-4fb9-9aca-74b093f68a7b-link">4</a></sup></td><td class="has-text-align-center" data-align="center">1,500 : 1 <sup data-fn="a2835b86-4009-45a0-954b-86fce0627424" class="fn"><a href="#a2835b86-4009-45a0-954b-86fce0627424" id="a2835b86-4009-45a0-954b-86fce0627424-link">5</a></sup></td><td class="has-text-align-center" data-align="center">2,000 : 1</td></tr><tr><td>Peak sustained brightness</td><td class="has-text-align-center" data-align="center"><strong>1,600 (≤ 25℃)</strong></td><td class="has-text-align-center" data-align="center">450<sup data-fn="7cc0e42f-875c-4148-8ce7-17bbf1b8e484" class="fn"><a href="#7cc0e42f-875c-4148-8ce7-17bbf1b8e484" id="7cc0e42f-875c-4148-8ce7-17bbf1b8e484-link">6</a></sup></td><td class="has-text-align-center" data-align="center">450</td><td class="has-text-align-center" data-align="center">450</td><td class="has-text-align-center" data-align="center">500</td></tr><tr><td>Maximum &#8220;black&#8221; luminence</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="d231f696-9847-4c87-b843-921d7335e588" class="fn"><a href="#d231f696-9847-4c87-b843-921d7335e588" id="d231f696-9847-4c87-b843-921d7335e588-link">7</a></sup></td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="ff1bcdce-6782-4a98-af23-0f60b16ebd43" class="fn"><a href="#ff1bcdce-6782-4a98-af23-0f60b16ebd43" id="ff1bcdce-6782-4a98-af23-0f60b16ebd43-link">8</a></sup></td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="f3bcfdba-aa27-4817-8525-e3eab046df92" class="fn"><a href="#f3bcfdba-aa27-4817-8525-e3eab046df92" id="f3bcfdba-aa27-4817-8525-e3eab046df92-link">9</a></sup></td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Bit depth</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td></tr><tr><td>Display P3 coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">98.7%</a> <sup data-fn="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b" class="fn"><a href="#1c5cb586-f38c-4cb7-b885-03d2d3b75f9b" id="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b-link">10</a></sup></td><td class="has-text-align-center" data-align="center">98%</td><td class="has-text-align-center" data-align="center"><strong>99%</strong></td><td class="has-text-align-center" data-align="center">98%</td><td class="has-text-align-center" data-align="center">99%</td></tr><tr><td>Adobe RGB coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">96.7%</a></td><td class="has-text-align-center" data-align="center"><strong>99.5%</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Rec 709 coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>sRGB coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">94.3%</a></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center">99%</td></tr><tr><td>Refresh rate</td><td class="has-text-align-center" data-align="center">47.95 – 60.00 Hz</td><td class="has-text-align-center" data-align="center">30 &#8211; 60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td></tr><tr><td>USB Power Delivery</td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center"><strong>140W</strong></td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center">100W</td></tr><tr><td>Connectivity</td><td class="has-text-align-center" data-align="center">1⨉ Thunderbolt 3<br>3⨉ USB-C (5 Gb/s<sup data-fn="4d2bc830-3dc1-4bc2-aed3-38a307275750" class="fn"><a href="#4d2bc830-3dc1-4bc2-aed3-38a307275750" id="4d2bc830-3dc1-4bc2-aed3-38a307275750-link">11</a></sup>)</td><td class="has-text-align-center" data-align="center"><strong>2⨉ Thunderbolt 5<br></strong>3⨉ USB-C (10 Gb/s, 1 up 2 down)<br>1⨉ DisplayPort 2.1<br>1⨉ HDMI 2.1</td><td class="has-text-align-center" data-align="center">2⨉ Thunderbolt 4<br><strong>5⨉ USB-C (10 Gb/s, 1 up 4 down)<br></strong>1⨉ Mini DisplayPort 2.?<br>1⨉ HDMI 2.?<br><strong>1⨉ 2.5 Gb Ethernet (RJ45)</strong></td><td class="has-text-align-center" data-align="center">2⨉ Thunderbolt 4<br>3⨉ USB-C (5 Gb/s, 1 up 2 down)<br>1x USB-C signal switch (for KVM)<br>1⨉ HDMI 2.1<br>1⨉ 3.5mm stereo audio (out)</td><td class="has-text-align-center" data-align="center">3⨉ USB-C (1 up 2 down)<br>1⨉ DisplayPort 2.1<br>2⨉ HDMI 2.1<br>1⨉ 3.5mm stereo audio (out)</td></tr><tr><td>Built-in KVM</td><td class="has-text-align-center" data-align="center">No</td><td class="has-text-align-center" data-align="center">No<sup data-fn="eb03d0d5-20a2-4377-9a37-dc051226db01" class="fn"><a href="#eb03d0d5-20a2-4377-9a37-dc051226db01" id="eb03d0d5-20a2-4377-9a37-dc051226db01-link">12</a></sup></td><td class="has-text-align-center" data-align="center"><strong>Yes</strong></td><td class="has-text-align-center" data-align="center"><strong>Yes</strong></td><td class="has-text-align-center" data-align="center">No</td></tr><tr><td>Dimensions (excluding stand)</td><td class="has-text-align-center" data-align="center">41.2 ⨉ 71.8 ⨉ 2.7 cm</td><td class="has-text-align-center" data-align="center"><strong>41 ⨉ 72 ⨉ 2.56 cm</strong></td><td class="has-text-align-center" data-align="center">49cm ⨉ 71cm ⨉ 6.6 cm</td><td class="has-text-align-center" data-align="center">41.97 ⨉ 71.42 ⨉ 4.69 cm</td><td class="has-text-align-center" data-align="center">41.5 ⨉ 71.2 ⨉ 2.5 cm</td></tr><tr><td>Naive volume<sup data-fn="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450" class="fn"><a href="#a6444b2a-9fb8-4cd2-ae9c-59baab4a2450" id="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450-link">13</a></sup> (excluding stand)</td><td class="has-text-align-center" data-align="center">7,987 cm³ </td><td class="has-text-align-center" data-align="center">7,559 cm³</td><td class="has-text-align-center" data-align="center">23,069 cm³</td><td class="has-text-align-center" data-align="center">14,058 cm³</td><td class="has-text-align-center" data-align="center"><strong>7,387 cm³</strong></td></tr><tr><td>Weight w/ stand</td><td class="has-text-align-center" data-align="center">11.8 kg</td><td class="has-text-align-center" data-align="center">9.48 kg</td><td class="has-text-align-center" data-align="center">13.29 kg</td><td class="has-text-align-center" data-align="center"><strong>9.3 kg</strong></td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Weight w/o stand</td><td class="has-text-align-center" data-align="center">7.48 kg</td><td class="has-text-align-center" data-align="center"><strong>5.99 kg</strong></td><td class="has-text-align-center" data-align="center">8.62 kg</td><td class="has-text-align-center" data-align="center">6.3 kg</td><td class="has-text-align-center" data-align="center">7.5 kg</td></tr><tr><td>Price w/o stand</td><td class="has-text-align-center" data-align="center">$4,999 US</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center"><strong>$1,799 US</strong></td></tr><tr><td>Price w/ stand</td><td class="has-text-align-center" data-align="center">$5,999 US</td><td class="has-text-align-center" data-align="center">$1,999 US</td><td class="has-text-align-center" data-align="center">$2,350 US</td><td class="has-text-align-center" data-align="center"><strong>$1,299 US</strong></td><td class="has-text-align-center" data-align="center">$1,898 US</td></tr><tr><td>Introduced</td><td class="has-text-align-center" data-align="center">December 2019</td><td class="has-text-align-center" data-align="center">October 2025</td><td class="has-text-align-center" data-align="center">May 2023</td><td class="has-text-align-center" data-align="center">August 2025</td><td class="has-text-align-center" data-align="center">July 2025?</td></tr><tr><td>Discontinued</td><td class="has-text-align-center" data-align="center">March 2026</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td></tr></tbody></table></figure>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ Note on Kuycon: I added the G32P after <a href="https://kevinyank.com" data-wpel-link="external" target="_blank" rel="external noopener">Kevin Yank</a> suggested it.  It&#8217;s a Chinese brand, which I would normally ignore for numerous reasons, <em>but</em> they actually have a functioning, well-designed English website, with a real working order system.  So you can actually buy one in the western world.  But be careful, nonetheless &#8211; there are plenty of anecdotes online about their non-existent customer support.  Also, I&#8217;m not sure it&#8217;s worth a mere $100, vs the LG, to take the risk &#8211; and lose Thunderbolt 5 &amp; KVM functionality &#8211; unless you <em>really</em> want a display that shamelessly rips off the appearance of the Pro Display XDR (but without any of the actual benefits, like HDR support and higher contrast ratio).<br><br>Also, there is a G32X which is similar but inferior &#8211; it has a lower contrast ratio and only 8-bit depth. </p>
</div></div>


<ol class="wp-block-footnotes"><li id="a041ec8b-df15-4e40-85ce-8259991341aa">Apple don&#8217;t appear to have ever published a brightness spec for the original Retina iMac, but <a href="https://www.tomsguide.com/us/apple-imac-5k-retina-2015,review-3115.html" data-wpel-link="external" target="_blank" rel="external noopener">Tom&#8217;s Guide measured their review model at 382 lumens</a>.<br><br>The comparison to the original Retina iMac is apt because it was the <em>first</em> of the retina [desktop] displays, of which these 6k displays are all members.  It also had the exact same pixel density &#8211; 218 PPI &#8211; as the Apple and Asus displays &#8211; i.e. each individual pixel is the exact same size &#8211; so it&#8217;s a very fair point of comparison despite the overall differences in resolution. <a href="#a041ec8b-df15-4e40-85ce-8259991341aa-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="08a8dafe-81b7-4c14-94ec-907400f178dc">Speculation is that the Asus uses an AUO panel (the same one that&#8217;s popular with Chinese display manufacturers, because it&#8217;s very cheap).  I wouldn&#8217;t normally give this much weight, except it is interesting that both the Asus and the Chinese displays are frequently reported as making an annoying whining noise at certain brightness levels, and have <em>identical</em> panel specifications, and were released at similar times (notably, many years after the Pro Display XDR). <a href="#08a8dafe-81b7-4c14-94ec-907400f178dc-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="27847762-88f0-44ea-ba59-70adf9684471">This is what LG states in the monitor&#8217;s specifications.  Yet, LG also states that this monitor is DisplayHDR 600 certified, which means <a href="https://displayhdr.org/performance-criteria/" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s <em>required</em></a> to have a static contrast ratio of at least 8,000 : 1. <a href="#27847762-88f0-44ea-ba59-70adf9684471-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="6a31577c-b563-4fb9-9aca-74b093f68a7b">As with the LG, Dell <em>states</em> a 2,000 : 1 contrast ratio even though DisplayHDR 600 conformance requires at least 8,000 : 1. <a href="#6a31577c-b563-4fb9-9aca-74b093f68a7b-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="a2835b86-4009-45a0-954b-86fce0627424">Asus <em>states</em> a 1,500 : 1 contrast ratio even though DisplayHDR 600 conformance requires at least 8,000 : 1.  Though they also describe the 1,500 : 1 as &#8220;typical&#8221; while also listing 3,000 : 1 as the maximum. <a href="#a2835b86-4009-45a0-954b-86fce0627424-link" aria-label="Jump to footnote reference 5">↩︎</a></li><li id="7cc0e42f-875c-4148-8ce7-17bbf1b8e484">LG says &#8220;typical&#8221; brightness is 450, with minimum being 360, without explaining the difference &#8211; e.g. whether that&#8217;s for a white patch vs full-screen white, or perhaps depending on ambient temperature.  I&#8217;m choosing to be generous and <em>assume</em> it&#8217;s merely a temperature thing, and not likely to be a concern in a typical indoor environment, because 360 is ridiculously dim.  <a href="#7cc0e42f-875c-4148-8ce7-17bbf1b8e484-link" aria-label="Jump to footnote reference 6">↩︎</a></li><li id="d231f696-9847-4c87-b843-921d7335e588">LG don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#d231f696-9847-4c87-b843-921d7335e588-link" aria-label="Jump to footnote reference 7">↩︎</a></li><li id="ff1bcdce-6782-4a98-af23-0f60b16ebd43">Dell don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#ff1bcdce-6782-4a98-af23-0f60b16ebd43-link" aria-label="Jump to footnote reference 8">↩︎</a></li><li id="f3bcfdba-aa27-4817-8525-e3eab046df92">Asus don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#f3bcfdba-aa27-4817-8525-e3eab046df92-link" aria-label="Jump to footnote reference 9">↩︎</a></li><li id="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b">Apple don&#8217;t state the actual coverage &#8211; just vaguely reference the various colour gamut standards &#8211; so these are the figures as actually tested by PCMag. <a href="#1c5cb586-f38c-4cb7-b885-03d2d3b75f9b-link" aria-label="Jump to footnote reference 10">↩︎</a></li><li id="4d2bc830-3dc1-4bc2-aed3-38a307275750">Only when used with Macs which support DSC (Display Stream Compression), otherwise the USB-C ports are limited to USB 2.0 (400 Mb/s). <a href="#4d2bc830-3dc1-4bc2-aed3-38a307275750-link" aria-label="Jump to footnote reference 11">↩︎</a></li><li id="eb03d0d5-20a2-4377-9a37-dc051226db01">The tech specs claim it has a built-in KVM, but there&#8217;s no mention of that anywhere else in the marketing materials nor the user manual.  It does have a USB hub, which you can <em>manually</em> switch between the Thunderbolt or USB upstream ports by diving into the on-screen display, but there&#8217;s no apparent support for (a) doing this automatically when the input source changes nor (b) switching input sources &amp; USB routing via keypress. <a href="#eb03d0d5-20a2-4377-9a37-dc051226db01-link" aria-label="Jump to footnote reference 12">↩︎</a></li><li id="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450">Meaning the simple product of the three maximal dimensions.  Some of these displays have curved backs, so their actual volume will be substantially less. <a href="#a6444b2a-9fb8-4cd2-ae9c-59baab4a2450-link" aria-label="Jump to footnote reference 13">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/6k-display-comparison/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8747</post-id>	</item>
		<item>
		<title>Studio Display XDR vs Pro Display XDR</title>
		<link>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/</link>
					<comments>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 03 Mar 2026 17:58:28 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Pro Display XDR]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Studio Display XDR]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8738</guid>

					<description><![CDATA[Studio Display XDR Pro Display XDR Screen diagonal 69 cm 81 cm (+17%) Resolution 5,120 ⨉ 2,880 6,016 ⨉ 3,384 Pixel count 14,745,600 20,358,144 (+38%) Backlight zones 2,304 (+300%) 576 Pixel density 218 218 Contrast ratio 1,000,000 : 1 1,000,000 : 1 Peak sustained brightness 2,000 (≤25℃) (+25%) 1,600 (≤ 25℃) Display P3 coverage ?&#8230; <a class="read-more-link" href="https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-table aligncenter is-style-stripes"><table><thead><tr><th></th><th class="has-text-align-center" data-align="center">Studio Display XDR</th><th class="has-text-align-center" data-align="center">Pro Display XDR</th></tr></thead><tbody><tr><td>Screen diagonal</td><td class="has-text-align-center" data-align="center">69 cm</td><td class="has-text-align-center" data-align="center">81 cm (+17%)</td></tr><tr><td>Resolution</td><td class="has-text-align-center" data-align="center">5,120 ⨉ 2,880</td><td class="has-text-align-center" data-align="center"><strong>6,016 ⨉ 3,384</strong></td></tr><tr><td>Pixel count</td><td class="has-text-align-center" data-align="center">14,745,600</td><td class="has-text-align-center" data-align="center"><strong>20,358,144</strong> (+38%)</td></tr><tr><td>Backlight zones</td><td class="has-text-align-center" data-align="center"><strong>2,304</strong> (+300%)</td><td class="has-text-align-center" data-align="center">576</td></tr><tr><td>Pixel density</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">218</td></tr><tr><td>Contrast ratio</td><td class="has-text-align-center" data-align="center">1,000,000 : 1</td><td class="has-text-align-center" data-align="center">1,000,000 : 1</td></tr><tr><td>Peak sustained brightness</td><td class="has-text-align-center" data-align="center"><strong>2,000</strong> (≤25℃) (+25%)</td><td class="has-text-align-center" data-align="center">1,600 (≤ 25℃)</td></tr><tr><td>Display P3 coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">98.7%</a></td></tr><tr><td>Adobe RGB coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">96.7%</a></td></tr><tr><td>sRGB coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">94.3%</a></td></tr><tr><td>Refresh rate</td><td class="has-text-align-center" data-align="center"><strong>47 &#8211; 120 Hz</strong></td><td class="has-text-align-center" data-align="center">47.95 &#8211; 60.00 Hz</td></tr><tr><td>USB Power Delivery</td><td class="has-text-align-center" data-align="center"><strong>140W</strong> (+46%)</td><td class="has-text-align-center" data-align="center">96W</td></tr><tr><td>Connectivity</td><td class="has-text-align-center" data-align="center"><strong>Thunderbolt 5 (1 up, 1 down) + 2 USB-C (10 Gb/s)</strong></td><td class="has-text-align-center" data-align="center">Thunderbolt 3 (1 up) + 3 USB-C (5 Gb/s<sup data-fn="e8feb3fe-feb2-4a81-83f9-5562e245b289" class="fn"><a href="#e8feb3fe-feb2-4a81-83f9-5562e245b289" id="e8feb3fe-feb2-4a81-83f9-5562e245b289-link">1</a></sup>)</td></tr><tr><td>Dimensions (excluding stand)</td><td class="has-text-align-center" data-align="center">36.2 ⨉ 62.3 ⨉ 3.3 cm</td><td class="has-text-align-center" data-align="center">41.2 ⨉ 71.8 ⨉ 2.7 cm</td></tr><tr><td>Volume (excluding stand)</td><td class="has-text-align-center" data-align="center">7,442 cm³</td><td class="has-text-align-center" data-align="center">7,987 cm³ (+7%)</td></tr><tr><td>Weight w/ stand</td><td class="has-text-align-center" data-align="center">8.5 kg</td><td class="has-text-align-center" data-align="center">11.8 kg (+39%)</td></tr><tr><td>Weight w/o stand</td><td class="has-text-align-center" data-align="center">6.3 kg</td><td class="has-text-align-center" data-align="center">7.48 kg (+19%)</td></tr><tr><td>Price w/o stand</td><td class="has-text-align-center" data-align="center"><s>$3,299</s> $2,899 US</td><td class="has-text-align-center" data-align="center">$4,999 US (<s>+52%</s> +72%)</td></tr><tr><td>Price w/o stand w/ nano texture</td><td class="has-text-align-center" data-align="center"><s>$3,599</s> $3,199 US</td><td class="has-text-align-center" data-align="center">$5,999 US (<s>+67%</s> +88%)</td></tr></tbody></table></figure>



<p>All in all… meh.</p>



<p>28% fewer pixels for <s>34%</s> 42% fewer dollars (47% if you&#8217;re talking nano-textured) &#8211; so technically better value, if you don&#8217;t really care about screen real-estate. But that extra real estate is really valuable, <em>and</em> Apple have now apparently ceded the large display market to… well, mostly the tumbleweeds. Sure, <a href="https://wadetregaskis.com/6k-display-comparison/" data-type="post" data-id="8747" data-wpel-link="internal">there&#8217;s <em>technically</em> other 6k displays</a>, like the <a href="https://www.lg.com/us/monitors/lg-32u990a-s-ultrafine-monitor" data-wpel-link="external" target="_blank" rel="external noopener">LG</a>, the <a href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-6k-monitor-u3224kb/apd/210-bhbz/monitors-monitor-accessories" data-wpel-link="external" target="_blank" rel="external noopener">Dell</a>, or the <a href="https://shop.asus.com/us/90lm0bd0-b01kb2-proart-display-6k-pa32qcv.html" data-wpel-link="external" target="_blank" rel="external noopener">Asus</a>, but while they have some advantages &#8211; less than half the price, most notably &#8211; they have real big disadvantages &#8211; like low brightness and poor contrast ratios.</p>



<p>4⨉ the backlight zones is a significant improvement, I&#8217;ll grant Apple that.  But it doesn&#8217;t eliminate the blooming that was problematic with the Pro Display XDR, merely reduces it.  In an era of OLED displays &#8211; hell, my old LG TV from nearly a decade ago has an OLED display; this ain&#8217;t new technology &#8211; a brand new, ostensibly-high-end &#8220;studio&#8221; display still running on LED backlighting is just sad.</p>



<p>The extra brightness of the Studio Display XDR is merely nice &#8211; an extra 25% isn&#8217;t a big difference (certainly nothing like the +200% or so in going from a competing 6k display to the Apple Pro Display XDR).  Props to Apple for the improvement, but it&#8217;s minor.</p>



<p>It&#8217;s interesting that the new, smaller, lower-resolution Studio Display XDR is nearly the same spatial volume as its big sister (and not as much lighter as its reduced resolution and screen dimensions would suggest).  I wonder if that&#8217;s dictated by thermals?</p>



<p>I didn&#8217;t bother including the audio &amp; camera aspects because I&#8217;m genuinely confused as to who, in the market for an expensive display, would care?  If you&#8217;re doing photography there&#8217;s no sound anyway, and if you&#8217;re doing videography in this price range you should be using real speakers or headphones.</p>



<p>And the camera… sigh… I miss the <a href="https://paulstamatiou.com/gear/apple-isight" data-wpel-link="external" target="_blank" rel="external noopener">iSight</a>, which gave you a much better camera &#8211; thanks to physics &#8211; that you could <em>optionally</em> buy and use.  And look at it &#8211; it was a beautiful design that functioned <em>really well</em>, that would have fit in perfectly with the Pro Display XDR!</p>



<p>I&#8217;m also choosing to overlook the firmware, which I assume uses the same weird, bastardised, glitchy version of iOS as the prior Studio Display model.</p>



<p>After <em>more than six years</em>, I was hoping for an improved Pro Display XDR, not merely a small version.</p>


<ol class="wp-block-footnotes"><li id="e8feb3fe-feb2-4a81-83f9-5562e245b289">Only when used with Macs which support DSC (Display Stream Compression), otherwise the USB-C ports are limited to USB 2.0 (400 Mb/s). <a href="#e8feb3fe-feb2-4a81-83f9-5562e245b289-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8738</post-id>	</item>
		<item>
		<title>Bugs Apple Loves &#038; Apps Apple Hates</title>
		<link>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/</link>
					<comments>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 26 Jan 2026 01:48:06 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8717</guid>

					<description><![CDATA[It&#8217;s been a while since I&#8217;ve seen such a pithy and accurate representation of what it&#8217;s like being an Apple Mac &#38; iPhone user these days (well done Nick Hodulik!). The externalities cost estimates might be a little tongue-in-cheek, but honestly, are they all that wrong? One small irritation at the wrong moment can ricochet&#8230; <a class="read-more-link" href="https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It&#8217;s been a while since I&#8217;ve seen such <a href="https://www.bugsappleloves.com" data-wpel-link="external" target="_blank" rel="external noopener">a pithy and accurate representation of what it&#8217;s like being an Apple Mac &amp; iPhone user these days</a> (well done <a href="https://github.com/nhod" data-wpel-link="external" target="_blank" rel="external noopener">Nick Hodulik</a>!).</p>



<p>The externalities cost estimates might be a little tongue-in-cheek, but honestly, are they all that wrong?  One small irritation at the wrong moment can ricochet my happy mood off into the doldrums, and Apple&#8217;s products produce a hundred &#8220;small&#8221; irritations every day &#8211; which compound in their irritation when you see them software update after software update, year after year, product after product.  It&#8217;s hard not to take it personally.  Like Apple is <em>deliberately</em> being cruel.</p>



<p><a href="https://ikennd.ac/about/" data-wpel-link="external" target="_blank" rel="external noopener">Daniel Kennett</a> wrote, in <a href="https://ikennd.ac/blog/2026/01/old-man-yells-at-modern-software-design/" data-wpel-link="external" target="_blank" rel="external noopener">his memorial to Aperture</a>, about how the Mac-using experience wasn&#8217;t <em>actually</em> rainbows and perfection even back in whatever you personally feel was the golden age (I&#8217;m with him that circa System 7.1 was glorious, though the early days of Mac OS X were also very special to me).  Which is true &#8211; fire up your favourite old Mac on <a href="https://infinitemac.org" data-wpel-link="external" target="_blank" rel="external noopener">Infinite Mac</a> and see the strength of your rose-tinted nostalgia glasses.</p>



<p><em>But</em>, the big difference is that Apple back then was a relatively tiny company struggling just to survive in a brutal industry dominated by <a href="https://www.ibm.com" data-wpel-link="external" target="_blank" rel="external noopener">soulless</a>, <a href="https://www.microsoft.com" data-wpel-link="external" target="_blank" rel="external noopener">greedy</a> <a href="https://www.intel.com" data-wpel-link="external" target="_blank" rel="external noopener">monsters</a>.</p>



<p>There is a point at which mere indifference or incompetence transitions into negligence, and it&#8217;s <em>long</em> before you become one of the wealthiest companies on the planet with a veritable army of engineers.</p>



<p>Having worked at Apple &#8211; among other big tech companies &#8211; I can say with confidence that there&#8217;s no valid reason why they cannot fix long-standing, <em>infamous</em> bugs.  It&#8217;s.  Not.  That.  Hard.  One half-decent engineer could fix everything listed on Bugs Apple Loves in six months, single-handed.</p>



<p>It&#8217;s not apparent why that doesn&#8217;t happen, but it&#8217;s not that Apple are technically incapable of it, and it cannot be that they&#8217;re unaware, so it must be that they&#8217;re choosing not to.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8717</post-id>	</item>
		<item>
		<title>Fixing sudden, random iPhone disconnects from Image Capture</title>
		<link>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/</link>
					<comments>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 21 Jan 2026 18:33:09 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Image Capture]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Tethering]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8666</guid>

					<description><![CDATA[It appears that each time tethering is enabled or disabled on the iPhone, it disconnects Image Capture. So if you have spotty cellular service &#8211; because perhaps you live in the United States, where that&#8217;s the only kind of cellular service on offer &#8211; you might find that happens so often that you can&#8217;t complete&#8230; <a class="read-more-link" href="https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It appears that each time tethering is enabled or disabled on the iPhone, it disconnects Image Capture.  So if you have spotty cellular service &#8211; because perhaps you live in the United States, where that&#8217;s the only kind of cellular service on offer &#8211; you might find that happens so often that you can&#8217;t complete basic media transfers in Image Capture.</p>



<p>Thankfully the workaround is simple &#8211; disable tethering, or enable Airplane mode, while you&#8217;re using Image Capture.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/01/camera-disconnected-while-importing-error-dialog.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8666</post-id>	</item>
		<item>
		<title>Better image stabilisation in Final Cut Pro using Object Tracking</title>
		<link>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/</link>
					<comments>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 18 Jan 2026 20:22:54 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Final Cut Pro]]></category>
		<category><![CDATA[Object Tracking]]></category>
		<category><![CDATA[stabilisation]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8656</guid>

					<description><![CDATA[This is basically a short written set of instructions derived from Cody Wanner&#8216;s YouTube video on the topic, refined a little for simplicity and updated for the newer GUI in more recent versions of Final Cut Pro. So feel free to view that video if you prefer that medium. I just find I need to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is basically a short written set of instructions derived from <a href="https://www.youtube.com/@RCodyWanner" data-wpel-link="external" target="_blank" rel="external noopener">Cody Wanner</a>&#8216;s <a href="https://www.youtube.com/watch?v=JstYyYT4OzM" data-wpel-link="external" target="_blank" rel="external noopener">YouTube video on the topic</a>, refined a little for simplicity and updated for the newer GUI in more recent versions of Final Cut Pro.  So feel free to view that video if you prefer that medium.  I just find I need to reference this sporadically and it&#8217;s easier to just re-read written instructions that re-watch a whole video.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p><strong>Background</strong>: Final Cut Pro&#8217;s built-in image stabilisation is a bit unreliable.  Sometimes it works perfectly, just like you&#8217;d expect.  Most of the time it requires manual tweaking and futzing in order to get good-enough results.  And sometimes it just <em>does not work</em>, no matter what you do, for reasons that are beyond me.  The technique shown here is annoyingly laborious to execute, but it works not just more reliably but also often just better (if your objective is <em>complete</em> stabilisation, at least).</p>
</div></div>



<p>Steps:</p>



<ol class="wp-block-list">
<li>Duplicate the clip (option-drag it in the timeline view) and place the duplicate above<sup data-fn="6883a475-8404-4bfe-a857-098915a99cc4" class="fn"><a href="#6883a475-8404-4bfe-a857-098915a99cc4" id="6883a475-8404-4bfe-a857-098915a99cc4-link">1</a></sup> the original.  Ensure it&#8217;s perfectly aligned on the timeline&#8217;s X (time) axis, otherwise it won&#8217;t show up as a tracking source in step 3.2.</li>



<li>On the duplicate clip (make sure it&#8217;s selected and your playback position is within it so that you can see what you&#8217;re doing!):
<ol class="wp-block-list">
<li>Invert the scale (e.g. make it -100% instead of the default 100%).</li>



<li>Invert the X &amp; Y offsets (if necessary &#8211; if they were non-zero beforehand).</li>



<li>Add an Object Tracker (the + icon to the right of the &#8220;Tracker&#8221; titlebar at the bottom of the Video Inspector (right-hand pane)).</li>



<li>A white grid &#8211; your anchor section &#8211; should appear over your clip.  Move and resize it to have it cover an appropriate part of the clip (a subsection that&#8217;s contrasty and contains object(s) that are stable &#8211; in appearance and position &#8211; within the world space of the scene, and ideally are never obscured by anything during the clip).
<ul class="wp-block-list">
<li>☝️ You can adjust the timeline position of the clip to find the optimum frame in which to identify your anchor section.</li>
</ul>
</li>



<li>Click Analyze.
<ul class="wp-block-list">
<li>⚠️ This will sometimes not work if certain other operations are outstanding, such as dominant motion analysis for the clip &#8211; you have to either cancel those background tasks or wait for them to finish. One of Final Cut Pro&#8217;s many irritating bugs.</li>



<li>⚠️ Watch carefully as it works through the clip (forwards from your starting point, then backwards, as necessary), for:
<ul class="wp-block-list">
<li>Your anchor section being intruded upon by any moving objects within the scene.  If that happens, try to go back and refine your anchor section placement so that it won&#8217;t be intruded upon.  If that&#8217;s impossible, you can proceed but be aware that the results may be subpar.</li>



<li>The anchor section changing in position and size &#8211; the more it &#8216;wobbles&#8217;, the worse the final results are likely to be.  Consider different anchor section placement, or try a different analysis method (in the Video Inspector, for your Object Track, change the Analysis Method from the default, &#8220;Automatic&#8221;, to another option &#8211; note that you must click Analyze again after changing this, for it to take effect).</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>



<li>On the original clip:
<ol class="wp-block-list">
<li>Open the Transform viewer (the rectangle icon in the &#8220;Transform&#8221; titlebar in the Video Inspector).</li>



<li>Click the downward chevron next to &#8220;Tracker&#8221; text on the Tracker tab button (at the top of the video preview view), to bring up the configuration pop-up:
<ul class="wp-block-list">
<li>Set Tracker Source to your duplicate clip.</li>



<li>Set Tracker to the Object Tracker (or whatever you renamed it to).</li>



<li>Set the axes you want stabilisation to apply to (the Apply Tracker To checkboxes).</li>
</ul>
</li>
</ol>
</li>



<li>Disable the duplicate (e.g. V key while it&#8217;s selected).
<ul class="wp-block-list">
<li>☝️ You cannot have it disabled before you select it as the Tracker Source, as it won&#8217;t show up in the pop-up menu while disabled.</li>
</ul>
</li>



<li>On the original clip:
<ol class="wp-block-list">
<li>Set the scale to -100%.</li>



<li>Adjust the X &amp; Y positions to correct the framing, if necessary (if your X &amp; Y position were both 0 on the original clip, this shouldn&#8217;t be necessary).  Note that this usually isn&#8217;t a simple sign inversion as in similar previous steps.</li>
</ol>
</li>
</ol>



<p>Optional sixth step:  file a bug report or suggestion with Apple asking for them to make their built-in image stabilisation work better, and/or make the object tracker GUI easier to use (it could be just one or two clicks, to enable it and say &#8220;lock this part of the scene in place&#8221;).</p>


<ol class="wp-block-footnotes"><li id="6883a475-8404-4bfe-a857-098915a99cc4">Technically it doesn&#8217;t matter if it&#8217;s above or below, I just find it slightly more convenient when it&#8217;s above as usually it&#8217;ll be disabled (showing the original, now-stabilised version from below) but I access it easily by simply enabling it (V key), such as if I want to adjust the tracking. <a href="#6883a475-8404-4bfe-a857-098915a99cc4-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8656</post-id>	</item>
		<item>
		<title>How to make a macOS screen saver</title>
		<link>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/</link>
					<comments>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 06 Dec 2025 22:46:22 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[com.apple.screensaver.willstop]]></category>
		<category><![CDATA[SaveHollywood]]></category>
		<category><![CDATA[Screen saver]]></category>
		<category><![CDATA[ScreenSaverView]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8580</guid>

					<description><![CDATA[First, make sure you really want to. macOS&#8217;s screen saver system is absurdly buggy and broken. It&#8217;s frustrating to work with and very difficult to make work right. If you&#8217;re determined, read on. Screen savers are basically just applications. Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;. Setting up the&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-make-a-macos-screen-saver/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>First, make sure you <em>really</em> want to.  macOS&#8217;s screen saver system is absurdly buggy and broken.  It&#8217;s frustrating to work with and very difficult to make work right.</p>



<p>If you&#8217;re determined, read on.</p>



<p>Screen savers are basically just applications.  Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;.</p>



<h2 class="wp-block-heading">Setting up the Xcode project</h2>



<p>In Xcode, create a new project using the <code>Screen Saver</code> template.</p>



<p>Delete the Objective-C code &amp; header files it creates by default (unless, I suppose, you want to write your screen saver in Objective-C &#8211; woo, retro! 😆).</p>



<p>You need to import the ScreenSaver module (framework), subclass ScreenSaverView, and implement a couple of method overrides.  Here&#8217;s the basic skeleton:</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)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import ScreenSaver

class MyScreenSaver: ScreenSaverView {
    override init?(frame: NSRect, isPreview: Bool) {
        super.init(frame: frame, isPreview: isPreview)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        // TODO
    }

    override func startAnimation() {
        super.startAnimation()

        // TODO
    }

    override func animateOneFrame() {  // Optional.
        // TODO
    }

    override func stopAnimation() { //  Only for the live preview in System Settings.
        // TODO

        super.stopAnimation()
    }

    override var hasConfigureSheet: Bool {
        true
    }

    private var configureSheetController: ConfigureSheetController?

    override var configureSheet: NSWindow? {
        configureSheetController = ConfigureSheetController(windowNibName: "ConfigureSheet")
        return configureSheetController?.window
    }
}

class ConfigureSheetController: NSWindowController {
    override var windowNibName: NSNib.Name? {
        return "ConfigureSheet"
    }

    override func windowDidLoad() {
        super.windowDidLoad()

        // TODO
    }

    @IBAction func okButtonClicked(_ sender: NSButton) {
        // TODO

        window!.sheetParent!.endSheet(window!, returnCode: .OK)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">ScreenSaver</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">MyScreenSaver</span><span style="color: #000000">: ScreenSaverView {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: NSRect, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: </span><span style="color: #267F99">Bool</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: frame, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: isPreview)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: NSCoder) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: coder)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">startAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">animateOneFrame</span><span style="color: #000000">() {  </span><span style="color: #008000">// Optional.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() { </span><span style="color: #008000">//  Only for the live preview in System Settings.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> hasConfigureSheet: </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheetController: ConfigureSheetController?</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheet: NSWindow? {</span></span>
<span class="line"><span style="color: #000000">        configureSheetController = </span><span style="color: #795E26">ConfigureSheetController</span><span style="color: #000000">(</span><span style="color: #795E26">windowNibName</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> configureSheetController?.</span><span style="color: #001080">window</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ConfigureSheetController</span><span style="color: #000000">: NSWindowController {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> windowNibName: NSNib.Name? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@IBAction</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">okButtonClicked</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">sender</span><span style="color: #000000">: NSButton) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        window!.</span><span style="color: #001080">sheetParent</span><span style="color: #000000">!.</span><span style="color: #795E26">endSheet</span><span style="color: #000000">(window!, </span><span style="color: #795E26">returnCode</span><span style="color: #000000">: .</span><span style="color: #001080">OK</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Providing a preferences sheet</h2>



<p>If you don&#8217;t have any options to configure, you can of course change <code>hasConfigureSheet</code> to return <code>false</code>.  Otherwise, you&#8217;ll need to create a &#8220;ConfigureSheet&#8221; xib file with a window in it containing your screen saver&#8217;s settings.  You can use UserDefaults to save your settings (if you wish), same as any other app.  And you&#8217;ll need to add an &#8220;Okay&#8221; or &#8220;Save&#8221; or similar button to dismiss the sheet.</p>



<h2 class="wp-block-heading">Getting ready to render</h2>



<p>The key methods to implement are <code>setup</code>, with any initial configuration you wish to do (e.g. allocate image or video views, load assets, set up the view hierarchy, etc).</p>



<p><code>ScreenSaverView</code> is an <code>NSView</code> subclass with a flat black background by default, onto which you can add subviews.  Typically for a screen saver you have a very simple view hierarchy &#8211; often just a single view or CoreAnimation layer that you&#8217;re rendering to &#8211; but you can load a xib and insert elements from it into the view if you like.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ It&#8217;s wise to <em>not</em> render anything in <code>setup</code>, which includes accidentally &#8211; you might need to set views to hidden, layers to zero opacity, etc.  This is basically because there can be an arbitrarily long gap between <code>setup</code> and <code>startAnimation</code> calls, and it&#8217;s often weird to render something initially, potentially not animate for a noticeable length of time, and <em>then</em> start actually working properly.</p>



<p>Alternatively, you might insert a placeholder image or text, e.g. &#8220;Loading…&#8221;, if you really want.  But in my opinion it&#8217;s more graceful to just let the initial black screen stand for a moment.</p>
</div></div>



<h2 class="wp-block-heading">Rendering</h2>



<p><code>startAnimation</code> is where you should actually start displaying things.  e.g. if you&#8217;re using an <code>AVPlayer</code>, this is where you actually start it playing (after making it visible, e.g. setting its opacity to 1).</p>



<p><code>animateOneFrame</code> is optional, and is only called if you set the <code>animationTimeInterval</code> property (on <code>self</code>) to a finite, non-zero value in <code>setup</code> (in which case it&#8217;ll be called at intervals <em>at least</em> that long &#8211; it might not be called as often as you desire if previous calls overrun or there&#8217;s other bottlenecks in the screen saver framework).  It&#8217;s essentially just a minor convenience vs having to explicitly set up an <code>NSTimer</code>.</p>



<p>Given how buggy Apple&#8217;s screen saver framework is, I suggest <em>not</em> relying on <code>animateOneFrame</code> if you can at all avoid it.  Even if that means setting up your own timer.  That way when they likely break that too in some future macOS release, your screen saver won&#8217;t necessarily break as well.</p>



<h3 class="wp-block-heading">Bonus topic: fading in</h3>



<p>Unless your screen saver inherently appears gently (e.g. starts rendering with a flat black view and only slowly adds to it), it&#8217;s nice to add a fade-in.  You can do that using CoreAnimation on the view&#8217;s layer:</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)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>override func startAnimation() {
    // Other code…
    
    if let layer {
        layer.opacity = 0.0  // Should already be zero from `setup`, but just to be sure.

        let fadeAnimation = CABasicAnimation(keyPath: "opacity")
        fadeAnimation.fromValue = 0.0
        fadeAnimation.toValue = 1.0
        fadeAnimation.duration = 5  // Seconds.

        // Essential settings to keep the final state
        fadeAnimation.fillMode = .forwards
        fadeAnimation.isRemovedOnCompletion = false

        layer.add(fadeAnimation, forKey: "fadeAnimation")
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> layer {</span></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #001080">opacity</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span><span style="color: #000000">  </span><span style="color: #008000">// Should already be zero from `setup`, but just to be sure.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> fadeAnimation = </span><span style="color: #795E26">CABasicAnimation</span><span style="color: #000000">(</span><span style="color: #795E26">keyPath</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;opacity&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fromValue</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">toValue</span><span style="color: #000000"> = </span><span style="color: #098658">1.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">duration</span><span style="color: #000000"> = </span><span style="color: #098658">5</span><span style="color: #000000">  </span><span style="color: #008000">// Seconds.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Essential settings to keep the final state</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fillMode</span><span style="color: #000000"> = .</span><span style="color: #001080">forwards</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">isRemovedOnCompletion</span><span style="color: #000000"> = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #795E26">add</span><span style="color: #000000">(fadeAnimation, </span><span style="color: #795E26">forKey</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;fadeAnimation&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Note that you cannot implement a fade-out when the screen saver exits, because macOS hides your screen saver immediately.  Plus, the user might not want a fade-out as they may be in a rush to do something on their computer.</p>



<h3 class="wp-block-heading">Previewing</h3>



<p>You can determine if you&#8217;re running for real or only the preview via the <code>isPreview</code> property (on <code>self</code>).  Many screen savers don&#8217;t care, but particularly if you save any persistent state, you might want to avoid doing that during preview.  For example, in a screen saver which plays a looping video and resumes where it last left off, you probably don&#8217;t want the preview to quietly advance the video.</p>



<h2 class="wp-block-heading">Stopping</h2>



<p><code>stopAnimation</code> is <em>only</em> used for the live preview thumbnail shown in the Screen Saver System Settings pane.  It is <em>never</em> called in normal operation of the screen saver (contrary to what Apple&#8217;s documentation says &#8211; Apple broke that in macOS Sonoma and later).</p>



<p>And that leads to the first path off the official track.  When the screen saver is dismissed by the user, <em>nothing</em> in Apple&#8217;s framework code does anything.  Your view continues to exist, <code>animateOneFrame</code> continues getting called, etc.  Your screen saver just runs in the background, its output not visible, but wasting CPU cycles and RAM.  Worse, if you have sound, that keeps playing.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🙏 A big thanks to <a href="https://stackoverflow.com/users/4103152/cwizou" data-wpel-link="external" target="_blank" rel="external noopener">cwizou</a> via <a href="https://stackoverflow.com/questions/66861833/audio-keeps-playing-after-screensaver-ends" data-wpel-link="external" target="_blank" rel="external noopener">StackOverflow</a> for documenting <a href="https://stackoverflow.com/a/67161635" data-wpel-link="external" target="_blank" rel="external noopener">the solution</a>, which I&#8217;ve summarised below.</p>
</div></div>



<p>To get around that, you need to register for the <code>com.apple.screensaver.willstop</code> notification, in <code>setup</code>, like so:</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)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  private func setup() {
    DistributedNotificationCenter.default.addObserver(self,
                                                      selector: #selector(willStop(_:)),
                                                      name: Notification.Name("com.apple.screensaver.willstop"),
                                                      object: nil)
}
    
@objc func willStop(_ notification: Notification) {
    stopAnimation()
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">willStop</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">name</span><span style="color: #000000">: Notification.</span><span style="color: #795E26">Name</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;com.apple.screensaver.willstop&quot;</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">willStop</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Note that you still need <code>stopAnimation</code> specifically, because in the live preview in System Settings you won&#8217;t receive that <code>com.apple.screensaver.willstop</code> notification (from the system&#8217;s point of view, the screen saver <em>isn&#8217;t</em> running &#8211; it&#8217;s merely previewing).</p>



<h2 class="wp-block-heading">Handling resumption</h2>



<p>Here&#8217;s the second big bug in Apple&#8217;s screen saver framework &#8211; every time the screen saver starts, your <code>ScreenSaverView</code> subclass is created again. But the old one doesn&#8217;t go anywhere. So now you have <em>two</em> copies running simultaneously, which is at the very least wasteful, and can easily lead to gnarly bugs and weird behaviour (e.g. if both are playing sound, or both modify persistent state).</p>



<p>There are essentially two ways to handle this:</p>



<ol class="wp-block-list">
<li>Kill your own process every time you stop animating.</li>



<li>Manually kill or lame-duck older views when a new one is initialised.</li>
</ol>



<p>Note that you <em>cannot</em> simply check at <code>MyScreenSaver</code> initialisation time if an instance already exists and if so fail initialisation (as is prescribed by <a href="https://zsmb.co/building-a-macos-screen-saver-in-kotlin/#macos-sonoma" data-wpel-link="external" target="_blank" rel="external noopener">this</a> otherwise excellent write-up of this problem), because if you don&#8217;t correctly initialise you&#8217;ll sometimes end up with <em>nothing</em> rendering or running (the screen saver framework appears to not gracefully handle initialisation failures).</p>



<p>Killing your own process can work but has some perils:</p>



<ul class="wp-block-list">
<li>If you kill your process in <code>stopAnimation</code> the screen will flash black momentarily before actually exiting screen saver mode, which is visually annoying.</li>



<li>If the screen saver is restarted rapidly after being interrupted, sometimes you&#8217;ll end up with nothing but a black screen (with no screen saver running).  There&#8217;s evidently some race condition in Apple&#8217;s screen saver system between screen saver processes exiting and being [re]launched.</li>
</ul>



<p>So I recommend not taking that approach.  Instead, you can lame-duck the old view instances.  They&#8217;ll stick around, which is a little wasteful of RAM, but as long as they&#8217;re not rendering or otherwise doing anything, they&#8217;re benign.</p>



<p>There are various ways to implement that, but one of the simpler ones is simply a notification between instances:</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)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>static let NewInstanceNotification = "com.myapp.MyScreenSaver.NewInstance";

var lameDuck = false

private func setup() {
    // Initial setup…
    
    NotificationCenter.default.post(name: MyScreenSaver.NewInstanceNotification, object: self)

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(neuter(_:)),
                                           name: MyScreenSaver.NewInstanceNotification,
                                           object: nil)

    // Further setup…
}

@objc func neuter(_ notification: Notification) {
    lameDuck = true

    stopAnimation()

    self.removeFromSuperview()

    // TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.

    NotificationCenter.default.removeObserver(self)
    DistributedNotificationCenter.default().removeObserver(self)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">static</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> NewInstanceNotification = </span><span style="color: #A31515">&quot;com.myapp.MyScreenSaver.NewInstance&quot;</span><span style="color: #000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> lameDuck = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Initial setup…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">post</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">, </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">neuter</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Further setup…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">neuter</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    lameDuck = </span><span style="color: #0000FF">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">removeFromSuperview</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #795E26">default</span><span style="color: #000000">().</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>You should check <code>lameDuck</code> at the start of methods like <code>startAnimation</code> or <code>animateOneFrame</code> and exit immediately if it&#8217;s set to <code>true</code>.  Unfortunately, Apple&#8217;s screen saver framework will still call those methods on old instances.</p>



<h2 class="wp-block-heading">Exiting</h2>



<p>Unfortunately Apple&#8217;s screen saver system will never terminate your screen saver process.  Worse, even if you do <em>nothing</em> yourself, Apple&#8217;s screen saver framework code will run in an infinite loop, wasting [a small amount of] CPU time.  So it&#8217;s not great to leave your screen saver process running indefinitely.</p>



<p>Thus, I implement an idle timeout in my screen savers, to have them exit if they&#8217;re not active for a while.  This can be done like:</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)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>@MainActor var idleTimeoutWorkItem: DispatchWorkItem? = nil

override func startAnimation() {
    // Other code…
    
    DispatchQueue.main.async {
        if let idleTimeoutWorkItem {
            idleTimeoutWorkItem.cancel()
        }

        idleTimeoutWorkItem = nil
    }
    
    // Other code…
}

override func stopAnimation() {
    // Other code…
    
    if !lameDuck {
        DispatchQueue.main.async {
            idleTimeoutWorkItem?.cancel()

            let workItem = DispatchWorkItem(block: {
                NSApplication.shared.terminate(nil)
            })
            
            idleTimeoutWorkItem = workItem
            
            DispatchQueue.main.asyncAfter(wallDeadline: .now() + 65, execute: workItem)
        }
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> idleTimeoutWorkItem: DispatchWorkItem? = </span><span style="color: #0000FF">nil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> idleTimeoutWorkItem {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        idleTimeoutWorkItem = </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> !lameDuck {</span></span>
<span class="line"><span style="color: #000000">        DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem?.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> workItem = </span><span style="color: #795E26">DispatchWorkItem</span><span style="color: #000000">(</span><span style="color: #795E26">block</span><span style="color: #000000">: {</span></span>
<span class="line"><span style="color: #000000">                NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #795E26">terminate</span><span style="color: #000000">(</span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            })</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem = workItem</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #795E26">asyncAfter</span><span style="color: #000000">(</span><span style="color: #795E26">wallDeadline</span><span style="color: #000000">: .</span><span style="color: #795E26">now</span><span style="color: #000000">() + </span><span style="color: #098658">65</span><span style="color: #000000">, </span><span style="color: #795E26">execute</span><span style="color: #000000">: workItem)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I chose the 65 second timeout somewhat arbitrarily.  I figure there&#8217;s a reasonable chance a user will unlock their screen to do something quick, then engage the screen saver again &#8211; all in the less than a minute &#8211; and the cost of idling in the background for an extra minute is small, compared to the cost of relaunching the whole app and reinitialising your renderer.</p>



<p>I added five extra seconds to reduce the probability of aligning with some one-minute timer (e.g. a spurious wake with the screen saver set to start automatically after one minute of no user activity).</p>



<p>You can adjust it however you like.</p>



<h2 class="wp-block-heading">Testing your screen saver</h2>



<p>Double-clicking the built product (your &#8220;.saver&#8221; app) will prompt the user to install it, replacing an old version if necessary.  So that works, though I find it faster to just manually copy the &#8220;.saver&#8221; app to <code>~/Library/Screen Savers</code>.  Just make sure to kill the existing <code>legacyScreenSaver</code> process, if necessary.</p>



<p>You can test it in System Settings, in the Screen Saver pane.  That&#8217;s the only place you can test the live preview part.</p>



<p>But otherwise, I found it easiest to just set one of the screen hot corners to start the screen saver, and use that immediately after copying the new &#8220;.saver&#8221; file into place.</p>



<p>Just be aware that the first time any new copy of the screen saver runs, macOS runs a verification on the bundle, which can take a while if your screen saver is non-trivial in size (e.g. if you bundle large image or video resources).  You&#8217;ll get a black screen with nothing happening, after invoking the screen saver, while that verification is running.</p>



<h2 class="wp-block-heading">Distributing your screen saver</h2>



<p>You don&#8217;t <em>have</em> to sign your screen saver, necessarily, but users will get some annoying error dialogs trying to run it, and will have to fiddle with things in System Settings &#8211; or, if they&#8217;re on a corporate Mac, they might not be able to run it at all.  So it&#8217;s preferable to just sign it.</p>



<p>Xcode doesn&#8217;t support signing screen savers like it does for plain app targets and the like.  So you have to do it manually via the command line, on the built product (your &#8220;.saver&#8221; app).  Thankfully it&#8217;s just two commands (once you have the appropriate stuff set up in your developer account &#8211; note that you will need a paid Apple Developer account, at $99/year).</p>



<p>Follow the instructions <a href="https://www.gabrieluribe.me/blog/how-to-distribute-a-screensaver-on-macos-2022" data-wpel-link="external" target="_blank" rel="external noopener">here</a>, but note that they&#8217;re missing the final step:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>xcrun stapler staple -v MyScreenSaver.saver</p>
</blockquote>



<p>Note that you run it against the screen saver itself, <em>not</em> the zip file.  The zip file&#8217;s just a hack to get Apple&#8217;s notary tool to accept the submission.  It&#8217;s the screen saver bundle itself that&#8217;s actually notarised and signed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ <code>notarytool</code> uploads your screen saver to Apple.  Be sure it doesn&#8217;t contain anything you&#8217;re not happy being potentially public (Apple will <em>presumably</em> try to keep your uploads private to Apple, and <em>might</em> not intentionally store them forever, but I wouldn&#8217;t bet my life on their confidentiality).</p>
</div></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p>That&#8217;s &#8220;it&#8221; in a superficial sense &#8211; if you&#8217;ve followed all this so far, you have a <em>roughly</em> working screen saver.</p>



<p>But there are a lot more bugs and nuances thereof that may afflict you, depending on what you&#8217;re doing in your screen saver.  So good luck. 😣</p>



<h2 class="wp-block-heading">Addendum: SaveHollywood</h2>



<p>I found that looking at existing open source screen savers was <em>partially</em> helpful, but also sometimes misleading.  e.g. for my most recent screen saver I basically just play a video in a loop (which <em>should</em> be embarrassingly trivial but took two weeks to get working properly, thanks to the aforementioned bugs in Apple&#8217;s frameworks, among many others).  In that case I looked at <a href="https://github.com/packagesdev/savehollywood" data-wpel-link="external" target="_blank" rel="external noopener">SaveHollywood</a>, a similar screen saver, for aid and ideas.</p>



<p>Unfortunately, SaveHollywood is abandoned and doesn&#8217;t work on recent versions of macOS.  The way it does some things is archaic and either not the best way or not functional at all.</p>



<p>Nonetheless, it did help with some of the higher-level aspects, above the screen saver machinery itself, like how to use <code>AVPlayer</code> in a screen saver.</p>



<p>So, <em>do</em> check out similar, existing screen savers (and of course just use them directly if they suit your needs!) but beware of obsolete or otherwise incorrect code.</p>



<h2 class="wp-block-heading">Addendum:  What this says about Apple</h2>



<p>What really troubles me about the screen saver system is what it says about Apple&#8217;s approach to the Mac, software, and their users.  Which is sadly just the same thing we&#8217;ve been seeing for years now.</p>



<p>Screen savers used to work fine.  There was an API established <em>long</em> ago that was lightweight, straight-forward, and effective.  All Apple had to do was <em>not break it</em>.</p>



<p>And <em>how</em> they broke it is troubling.  Perhaps it was prompted by some otherwise unrelated but well-meaning refactor &#8211; pulling screen saver code out into a separate, sandboxed process, perhaps, for improved system security.  Fine.  But if it&#8217;s worth changing it&#8217;s worth changing <em>properly</em>.  It&#8217;s very clear that whomever did the changes either (a) didn&#8217;t care that they broke things badly, or (b) didn&#8217;t care to check.</p>



<p>It&#8217;s that recurring theme of <em>not caring</em> that&#8217;s most disappointing in today&#8217;s Apple.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/12/screen-saver-system-settings-panel-with-my-screen-saver-selected.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8580</post-id>	</item>
		<item>
		<title>presentedWindowStyle is not windowStyle</title>
		<link>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/</link>
					<comments>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 23 Sep 2024 23:12:29 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Feedback Assistant]]></category>
		<category><![CDATA[Happy]]></category>
		<category><![CDATA[presentedWindowStyle]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[windowStyle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8460</guid>

					<description><![CDATA[This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event. But it might also help others with this confusing SwiftUI API. What&#8217;s the difference between presentedWindowStyle(_:) and windowStyle(_:)? Well, one does something, the other doesn&#8217;t, basically. I tried using the former, and observed that&#8230; <a class="read-more-link" href="https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event.  But it might also help others with this confusing SwiftUI API.</p>



<p>What&#8217;s the difference between <code><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">presentedWindowStyle(_:)</a></code> and <code><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">windowStyle(_:)</a></code>?</p>



<p>Well, one does something, the other doesn&#8217;t, basically.</p>



<p>I tried using the former, and observed that it never has any effect.  I filed FB14892608 about it, a month ago.  While the long delay isn&#8217;t great, the response I got today was actually pretty helpful:</p>



<pre class="wp-block-preformatted">We’re sorry you ran into trouble with that API.<br><br>To adjust the style of a window group’s windows, you have a couple of options. If all of the windows in the group should have the same style, then using the windowStyle() scene modifier is what you want:<br><br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>        }<br>        .windowStyle(.hiddenTitleBar)<br>    }<br>}<br><br>If you need to adjust the style on an individual basis using state for that window, there are also some related view modifiers:<br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>                .toolbar(removing: .title)<br>                .toolbarBackgroundVisibility(.hidden, for: .windowToolbar)<br>        }<br>    }<br>}</pre>



<p>A simple apology followed by clear and specific instructions on how to actually achieve what I wanted.  I genuinely applaud Apple &#8211; or perhaps I should more specifically thank the individual(s) within DTS that actually provided this response.  Either way, it was a very pleasant surprise.</p>



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



<p>Now, it still leaves unanswered the question of what <code>presentedWindowStyle(_:)</code> is supposed to be for, and in what situations it actually <em>does</em> anything.  So not a perfect reply, per se, but really the proper solution for that is either:</p>



<ol style="list-style-type:upper-alpha" class="wp-block-list">
<li>Fix the API to not have such confusingly similar modifiers.</li>



<li>Improve the documentation to:
<ul class="wp-block-list">
<li>Explicitly point out the other, similarly-named modifier.</li>



<li>Distinguish them.</li>
</ul>
</li>
</ol>



<p>Currently the documentation is just:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by this scene.</p><cite><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>windowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>Versus:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by interacting with this view.</p><cite><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>presentedWindowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>The latter might <em>technically</em> convey something important here, in the <em>created by interacting with this view</em> part, but so much in SwiftUI is modifiers stuck haphazardly in weird and arbitrary places, that I&#8217;m been conditioned to ignore the fact that I&#8217;m often applying modifiers to views that don&#8217;t actually affect those views.  And for all I know in SwiftUI parlance views <em>do</em> &#8220;create&#8221; and/or &#8220;interact&#8221; with their parent windows (a <em>lot</em> about SwiftUI is backwards, part of its nature as a declarative API).</p>



<p>Especially if that&#8217;s the first candidate API you stumble across, when searching for a way to style a window, it&#8217;s very easy to presume it&#8217;s the right API.  Why would there be <em>multiple</em> APIs for styling the window; why would you continue searching after finding one?</p>



<p>Similarly, the <code>presentedWindowStyle(_:)</code> variant is the <em>only</em> one that appears in Xcode&#8217;s auto-complete if you try to add the modifier to your main view, which is both where you sometimes <em>have</em> to add window-level modifiers and also just a really easy mistake to make (instead of adding the modifier to the <code><a href="https://developer.apple.com/documentation/swiftui/windowgroup" data-wpel-link="external" target="_blank" rel="external noopener">WindowGroup</a></code>, one indentation level up).</p>



<p>Lastly, it doesn&#8217;t help that I can&#8217;t find <em>any</em> situation in which <code>presentedWindowStyle(_:)</code> has any effect, even knowing it&#8217;s not intended to style the parent window. One might assume it&#8217;s somehow related to sheets or somesuch, but apparently not? Presumably I&#8217;m overlooking some use-case &#8211; or maybe it <em>doesn&#8217;t</em> actually do anything on macOS, only iDevices? I welcome clues or tips.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8460</post-id>	</item>
		<item>
		<title>NSPasteboard crashes due to unsafe, internal concurrent memory mutation when handling file promises</title>
		<link>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/</link>
					<comments>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 05:01:02 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[memory corruption]]></category>
		<category><![CDATA[NSItemProvider]]></category>
		<category><![CDATA[NSPasteboard]]></category>
		<category><![CDATA[NSPasteboardItem]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8369</guid>

					<description><![CDATA[This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it. NSPasteboard mutates itself simultaneously from the main thread and the global concurrent Dispatch pool, w.r.t. to its internal type cache. This is surprisingly trivial&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it.</p>
</div></div>



<p><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> mutates itself simultaneously from the main thread and the global concurrent <a href="https://developer.apple.com/documentation/DISPATCH" data-wpel-link="external" target="_blank" rel="external noopener">Dispatch</a> pool, w.r.t. to its internal type cache. This is surprisingly trivial to reproduce (sample code below) by just dropping, e.g. a file promise (such as by opening a PNG in Preview, revealing the thumbnails sidebar, and then dragging the thumbnail onto the sample project’s window).</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">import</span><span style="color: #000000"> </span><span style="color: #267F99">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">ContentView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">Rectangle</span><span style="color: #000000">().</span><span style="color: #795E26">onDrop</span><span style="color: #000000">(</span><span style="color: #795E26">of</span><span style="color: #000000">: NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000">, </span><span style="color: #795E26">isTargeted</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">) { </span><span style="color: #001080">_</span><span style="color: #000000"> </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"> pb = </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>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = pb.</span><span style="color: #001080">pasteboardItems</span><span style="color: #000000"> </span><span style="color: #008000">// Seems to be necessary for the crash.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000"> </span><span style="color: #008000">// Not strictly necessary for the crash, but seems to make it more likely. 🤷‍♂️</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Judging from the callstack that runs in the concurrent pool, this is specific to file promises (and that seems to match my experience &#8211; it only crashes for some test cases, all of which involve file promises being present in the drag pasteboard at the time of the drop).</p>



<p>Since this bug causes semi-random memory corruption, it manifests in a large number of ways &#8211; not all of which are all that helpful.  But at least one case I’ve seen a few times is helpful, as it clearly shows the offending internal <code>NSPasteboard</code> code running concurrently with itself, e.g.:</p>



<pre class="wp-block-preformatted">Main queue / thread:
#0	0x00007ff80108dab5 in _platform_bzero$VARIANT$Haswell ()
#1	0x000000010df8774d in GuardMalloc_mallocInternal ()
#2	0x00007ff90792542f in stack_logging_lite_malloc ()
#3	0x00007ff800ea8733 in _malloc_zone_malloc_instrumented_or_legacy ()
#4	0x00007ff800f39a72 in _vasprintf ()
#5	0x00007ff800f16922 in asprintf ()
#6	0x00007ff80125655a in -[NSObject(NSObject) __dealloc_zombie] ()
#7	0x00007ff8020e039a in -[NSConcreteMapTable dealloc] ()
#8	0x00007ff804876983 in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#9	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#10	0x00007ff804aad148 in NSCoreDragReceiveMessageProc ()
#11	0x00007ff807517b1a in CallReceiveMessageCollectionWithMessage ()
#12	0x00007ff8075124fa in DoMultipartDropMessage ()
#13	0x00007ff8075122ce in DoDropMessage ()
#14	0x00007ff8075159a9 in CoreDragMessageHandler ()
#15	0x00007ff8011d776b in __CFMessagePortPerform ()
#16	0x00007ff80113e5b7 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#17	0x00007ff80113e4ee in __CFRunLoopDoSource1 ()
#18	0x00007ff80113d166 in __CFRunLoopRun ()
#19	0x00007ff80113c112 in CFRunLoopRunSpecific ()
#20	0x00007ff80bb55a09 in RunCurrentEventLoopInMode ()
#21	0x00007ff80bb55646 in ReceiveNextEventCommon ()
#22	0x00007ff80bb55561 in _BlockUntilNextEventMatchingListInModeWithFilter ()
#23	0x00007ff8047acc61 in _DPSNextEvent ()
#24	0x00007ff8050c0dc0 in -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#25	0x00007ff80479e075 in -[NSApplication run] ()
#26	0x00007ff804771ff3 in NSApplicationMain ()
#27	0x00007ff90dc24557 in ___lldb_unnamed_symbol57096 ()
#28	0x00007ff90e31fe64 in ___lldb_unnamed_symbol104448 ()
#29	0x00007ff90e6e63ff in static SwiftUI.App.main() -&gt; () ()
#30	0x000000010dfa5cce in static NSPasteboardItem_CrashApp.$main() ()
#31	0x000000010dfa5d69 in main at /Users/SadPanda/Documents/NSPasteboardItem Crash/NSPasteboardItem Crash/NSPasteboardItem_CrashApp.swift:11
#32	0x00007ff800cd5366 in start ()

Dispatch concurrent queue (default QoS):
#0	0x00007ff80111b45c in -[__NSSetM addObject:] ()
#1	0x00007ff80487692e in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#2	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#3	0x00007ff804aa9597 in -[NSPasteboard _canRequestDataForType:index:usesPboardTypes:combinesItems:] ()
#4	0x00007ff804fdd161 in -[NSPasteboard _dataForType:index:usesPboardTypes:combinesItems:securityScoped:] ()
#5	0x00007ff804aa7c4b in -[NSPasteboardItem dataForType:] ()
#6	0x00007ff8055804af in -[NSFilePromiseReceiver receivePromisedFilesAtDestination:options:operationQueue:reader:] ()
#7	0x00007ff90e787b51 in ___lldb_unnamed_symbol131674 ()
#8	0x00007ff90e9bc340 in ___lldb_unnamed_symbol148147 ()
#9	0x00007ff8020d00ba in __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ ()
#10	0x00007ff8020cffb8 in -[NSBlockOperation main] ()
#11	0x00007ff8020cff4b in __NSOPERATION_IS_INVOKING_MAIN__ ()
#12	0x00007ff8020cf1ec in -[NSOperation start] ()
#13	0x00007ff8020cef0d in __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ ()
#14	0x00007ff8020cedde in __NSOQSchedule_f ()
#15	0x000000010e68ce7d in _dispatch_block_async_invoke2 ()
#16	0x000000010e67ca7b in _dispatch_client_callout ()
#17	0x000000010e67fa09 in _dispatch_continuation_pop ()
#18	0x000000010e67eae8 in _dispatch_async_redirect_invoke ()
#19	0x000000010e6906a9 in _dispatch_root_queue_drain ()
#20	0x000000010e6911ba in _dispatch_worker_thread2 ()
#21	0x000000010dfb832f in _pthread_wqthread ()
#22	0x000000010dfbebeb in start_wqthread ()</pre>



<p>There doesn’t appear to be any workaround (short of not supporting drops at all!).</p>



<p>The more complicated the drop handler the more likely it is to promptly crash upon drop &#8211; in my real code with a non-trivial handler, it’s virtually guaranteed to crash on the second drop containing a file promise, while in the vastly reduced sample code (above) it can take dozens of drops before it finally crashes outright.</p>



<p>I have not <em>directly</em> tested whether this <code>NSPasteboard</code> bug occurs in the absence of <a href="https://developer.apple.com/documentation/SwiftUI" data-wpel-link="external" target="_blank" rel="external noopener">SwiftUI</a>, so I don’t strictly know if the root cause is in <a href="https://developer.apple.com/documentation/appkit" data-wpel-link="external" target="_blank" rel="external noopener">AppKit</a> or SwiftUI. However, since most SwiftUI apps don’t support drag-and-drop, but plenty of AppKit ones do and manage to not crash when given the exact same test cases, I do strongly suspect SwiftUI is causing this somehow.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You may wonder why I’m directly accessing the drag pasteboard rather than using the <code><a href="https://developer.apple.com/documentation/foundation/nsitemprovider" data-wpel-link="external" target="_blank" rel="external noopener">NSItemProvider</a></code>s provided by SwiftUI. It’s because that API is horribly broken &#8211; in many cases the provided <code>NSItemProvider</code>(s) are duds that contain no actual data. So I have to use the drag pasteboard directly in order to stand any chance of supporting drag &amp; drop.</p>



<p>Also, the <code>NSItemProvider</code>-based API is harder to use and doesn&#8217;t support important aspects of drag-and-drop, like file promises (although, with <code>NSPasteboard</code> apparently corrupting itself when file promises are received, I guess none of Apple&#8217;s APIs do anymore 😔).</p>
</div></div>



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



<h3 class="wp-block-heading">Follow-up (September 12th, 2024)</h3>



<p>I actually received a response from Apple, from a real human (or at least a convincing AI).  Ultimately their response didn&#8217;t help as it contained some mistakes, but I&#8217;m hopeful there&#8217;ll be more follow-up and a productive conclusion.  And in the interim, they did assert a few things which are important to know, and are not otherwise documented by Apple:</p>



<ul class="wp-block-list">
<li>SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/ondrop(of:istargeted:perform:)-f15m" data-wpel-link="external" target="_blank" rel="external noopener">onDrop(of:isTargeted:perform:)</a></code> method makes no claims or promises as to what thread / queue it executes the closure on, and in fact according to the anonymous Apple engineer it <em>never</em> executes the closure on the main thread.<br><br>Now, while that may be the intent, the reality of that is wrong &#8211; in my experience it <em>always</em> executes the closure on the main thread (which makes a lot of sense to me as drag-and-drop event handling in AppKit has always been on the main thread in practice).<br><br>Nonetheless, Apple says one cannot rely on the current behaviour and should in fact assume it <em>never</em> executes on the main thread (though in practice that means you have to <em>check</em>, not assume, since if you blindly do something like <code>DispatchQueue.main.sync</code><code style="font-size: 15px;"> { … }</code> in your drop handler your code <em>will</em> deadlock, today).</li>



<li><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> is not safe to use outside the main thread / queue.<br><br>This isn&#8217;t documented anywhere public &#8211; not in <code>NSPasteboard</code>&#8216;s documentation itself, nor the ancient <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a> documentation.<br><br>I wouldn&#8217;t be surprised if it&#8217;s broadly true, as the AppKit APIs involving it always seemed main-thread centric anyway (all the handlers and delegate methods involving pasteboards are invoked on the main thread, in my experience).  And it&#8217;s generally best to assume everything in AppKit is main-thread-only unless it&#8217;s explicitly documented otherwise.<br><br>However, it&#8217;s important to note that Apple&#8217;s <em>own</em> code doesn&#8217;t follow this rule &#8211; e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsfilepromisereceiver" data-wpel-link="external" target="_blank" rel="external noopener">NSFilePromiseReceiver</a></code>, internally, uses <code>NSPasteboard</code> from the global concurrent queue.</li>
</ul>



<p>Even though Apple&#8217;s initial response to this bug report hasn&#8217;t been all that fruitful, I do want to emphasise the fact that they <em>did</em> respond, which was a pleasant surprise and very much appreciated.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8369</post-id>	</item>
		<item>
		<title>NSCopyObject, the griefer that keeps on griefing</title>
		<link>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/</link>
					<comments>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 16 Jul 2024 00:42:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ARC]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[copy(with:)]]></category>
		<category><![CDATA[fixupCopiedIvars]]></category>
		<category><![CDATA[NeXT]]></category>
		<category><![CDATA[NSAnimation]]></category>
		<category><![CDATA[NSCell]]></category>
		<category><![CDATA[NSCopying]]></category>
		<category><![CDATA[NSImageRep]]></category>
		<category><![CDATA[Objective-C]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8281</guid>

					<description><![CDATA[NSCopyObject is a very old Foundation function - pre-dating Mac OS X entirely; from the NeXT era - that was originally basically just memcpy, but now it's complicated. A lot more complicated… <a class="read-more-link" href="https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/foundation/1587928-nscopyobject" data-wpel-link="external" target="_blank" rel="external noopener">NSCopyObject</a></code> is a very old Foundation function &#8211; pre-dating Mac OS X entirely; from the NeXT era &#8211; that was <em>originally</em> basically just <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/memcpy.3.html" data-wpel-link="external" target="_blank" rel="external noopener">memcpy</a></code>, but now it&#8217;s complicated.  A lot more complicated.</p>



<h2 class="wp-block-heading">What <code>NSCopyObject</code> does</h2>



<p>Its implementation <em>currently</em> starts with essentially:</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)"><span role="button" tabindex="0" data-code="id NSCopyObject(id object, NSUInteger extraBytes, NSZone *zone) {
    if (nil == object) {
        return nil;
    }
    
    id copy = object_copy(object, extraBytes);
    object_setClass(copy, objc_opt_class(object));
    return copy;
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">id</span><span style="color: #000000"> </span><span style="color: #795E26">NSCopyObject</span><span style="color: #000000">(</span><span style="color: #0000FF">id</span><span style="color: #000000"> object, </span><span style="color: #267F99">NSUInteger</span><span style="color: #000000"> extraBytes, </span><span style="color: #267F99">NSZone</span><span style="color: #000000"> *zone) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> (</span><span style="color: #0000FF">nil</span><span style="color: #000000"> == object) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span><span style="color: #000000">;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">id</span><span style="color: #000000"> copy = </span><span style="color: #795E26">object_copy</span><span style="color: #000000">(object, extraBytes);</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">object_setClass</span><span style="color: #000000">(copy, </span><span style="color: #795E26">objc_opt_class</span><span style="color: #000000">(object));</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> copy;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…where <a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L9057" data-wpel-link="external" target="_blank" rel="external noopener">object_copy</a> et al are part of <a href="https://github.com/apple-oss-distributions/objc4" data-wpel-link="external" target="_blank" rel="external noopener">the Objective-C runtime</a>.  <code>object_copy</code> and its callees are not trivial, so I won&#8217;t repeat them here.  The key parts are:</p>



<ol class="wp-block-list">
<li>The malloc in <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L8981" data-wpel-link="external" target="_blank" rel="external noopener">_class_createInstance</a></code> (allocate space for the copy).</li>



<li>The <code>memmove</code> in <code>object_copy</code> (naively copy the raw bytes over).</li>



<li>The call from <code>object_copy</code> to <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-class.mm#L535" data-wpel-link="external" target="_blank" rel="external noopener">fixupCopiedIvars</a></code> (half-heartedly attempt to fix the damage).</li>
</ol>



<p><code>fixupCopiedIvars</code> is notable.  It was added by necessity when <a href="https://en.wikipedia.org/wiki/Automatic_Reference_Counting" data-wpel-link="external" target="_blank" rel="external noopener">ARC</a> was introduced to the Objective-C runtime, in Mac OS X 10.6 (Snow Leopard) in 2006.  ARC added metadata to Objective-C classes to convey which instance variables were retain-counted object references, so that it could manage them automagically at runtime (not just for copying objects, but more importantly for deallocating them).  <code>fixupCopiedIvars</code> uses that metadata to identify things it has to retain (strongly or weakly) in the new copy.</p>



<p>So that should work great, right?  The copy operation increments the retain count of all shared objects the new copy references, like you&#8217;d expect?</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="340" height="266" src="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" alt="Grumpy Cat frowning, with the caption &quot;NO&quot;." class="wp-image-8282" srcset="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif 340w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no-256x200.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no@2x.avif 680w" sizes="(max-width: 340px) 100vw, 340px" /></figure>
</div>


<p>That metadata is incomplete.  It only works for Objective-C ivars managed by ARC.  i.e. <em>not</em> C++ ivars or Swift stored properties, nor even Objective-C ivars that aren&#8217;t using ARC<sup data-fn="cf3bfb82-cbf6-40c4-817f-3092a63f4021" class="fn"><a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021" id="cf3bfb82-cbf6-40c4-817f-3092a63f4021-link">1</a></sup>.</p>



<h2 class="wp-block-heading">But I don&#8217;t use <code>NSCopyObject</code>…?</h2>



<p>Almost nobody <em>intentionally</em> uses <code>NSCopyObject</code>, but your superclass might, and therefore you might.  Ever subclassed <code><a href="https://developer.apple.com/documentation/appkit/nscell" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a></code> or <code><a href="https://developer.apple.com/documentation/appkit/nsanimation" data-wpel-link="external" target="_blank" rel="external noopener">NSAnimation</a></code>, for example?</p>



<p><a href="https://forums.swift.org/t/why-would-deinit-be-called-when-retain-count-is-non-zero/72924" data-wpel-link="external" target="_blank" rel="external noopener">I happened to hit this</a> when subclassing <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code> (and I&#8217;m very grateful to <a href="https://forums.swift.org/u/ksluder" data-wpel-link="external" target="_blank" rel="external noopener">Kyle Sluder</a> for so quickly identifying the problem &#8211; it could have taken me forever to figure it out, otherwise).</p>



<p>If your superclass uses <code>NSCopyObject</code>, it&#8217;s now your problem just as much as if you&#8217;d used <code>NSCopyObject</code> directly, whether you like it or not.</p>



<p>And even more problematically, whether you <em>know</em> it or not.  If your superclass is defined by a 3rd party framework / library, or anything that&#8217;s closed source, you might have no idea whether it uses <code>NSCopyObject</code> currently.  Worse, you have no control over whether it will or will not use it in future (though anyone that <em>adds</em> a use of <code>NSCopyObject</code> at this point had better hope the atheists are right).</p>



<h2 class="wp-block-heading">So how do I defend against <code>NSCopyObject</code>?</h2>



<h3 class="wp-block-heading">Objective-C</h3>



<p>Pre-ARC it used to be <em>relatively</em> easy to work around this, in Objective-C.  You &#8220;just&#8221; had to manually <code><a href="https://developer.apple.com/documentation/objectivec/1418956-nsobject/1571946-retain?language=objc" data-wpel-link="external" target="_blank" rel="external noopener">retain</a></code> all your subclasses&#8217; reference ivars &#8211; and manually copy some others, like non-ref-counted mutable or mortal buffers, etc.</p>



<p>But that generally isn&#8217;t possible with ARC &#8211; under which you cannot explicitly call <code>retain</code>.  Worse:</p>



<ul class="wp-block-list">
<li>There&#8217;s still <a href="https://www.mikeash.com/pyblog/friday-qa-2010-08-27-defensive-programming-in-cocoa.html" data-wpel-link="external" target="_blank" rel="external noopener">prominent</a> guides scattered about the web that push you unequivocally to use <code>retain</code>, which is not just impossible to do directly under ARC, but flat-out <em>wrong</em> even if you do figure out one of the &#8220;clever&#8221; ways to do it (you&#8217;ll end up <em>over</em>-retaining your ARC-managed references, causing memory leaks).</li>



<li>There&#8217;s also <a href="https://dohle.wordpress.com/2012/05/21/hello-world/" data-wpel-link="external" target="_blank" rel="external noopener">pages</a> lingering on the web that claim that merely turning on ARC will magically solve the problem (it <em>might</em>, but it&#8217;s not a panacea).</li>
</ul>



<p><a href="https://wiki.herzbube.ch/index.php/LearningObjectiveC#Object_copy" data-wpel-link="external" target="_blank" rel="external noopener">Some</a> <a href="https://robnapier.net/implementing-nscopying" data-wpel-link="external" target="_blank" rel="external noopener">guides</a> specify a better method, which is to manually zero out the copied object&#8217;s ivars and then repopulate them via formal property setters.  That actually works with or without ARC, although it may break &#8211; causing memory leaks &#8211; if the superclass ever stops using <code>NSCopyObject</code> (or if <code>NSCopyObject</code> ever gets upgraded to understand reference-counted ivars that it currently does not).  It&#8217;s also only possible in Objective-C because Swift doesn&#8217;t provide direct access to instance variables.</p>



<p>Keep in mind that any reference-typed ivars which are not strong or weak Objective-C objects managed by ARC will still, always need to be handled manually.  e.g. pointers to manually-managed memory buffers.</p>



<h3 class="wp-block-heading">Swift</h3>



<p>Ironically, Swift&#8217;s attempts to prevent incorrect code actually make it harder to write correct code in this case.  What you <em>want</em> to do is &#8211; like the Objective-C implementation &#8211; to just zero out the references and re-assign them like normal properties.  Zeroing them out <em>without triggering a release</em> basically undoes the mistaken <code>memcpy</code> that <code>NSCopyObject</code> did.  But Swift won&#8217;t let you.</p>



<p>Worse, <a href="https://github.com/swiftlang/swift/issues/47333" data-wpel-link="external" target="_blank" rel="external noopener">this has been known</a> for most of Swift&#8217;s existence and nothing has been done about it.</p>



<p>Simply setting the property to <code>nil</code> will cause it to be erroneously released, which may immediately deallocate the object and ultimately cause a crash or memory corruption.  Even if it doesn&#8217;t happen to deallocate the object, it&#8217;ll negate the retain you do during the assignment, making all your effort moot.</p>



<figure class="wp-block-pullquote"><blockquote><p>Strictly-speaking, the only safe thing to do is override <code><a href="https://developer.apple.com/documentation/foundation/nscopying/1410311-copy" data-wpel-link="external" target="_blank" rel="external noopener">copy(with:)</a></code> and not call super, but rather create a new instance from scratch.</p></blockquote></figure>



<p>That&#8217;s pretty heavy-handed, though, and not always possible (e.g. <code>NSImageRep</code>, as used by e.g. <code>NSBitmapImageRep</code>, does some special magic in its copy implementation which you cannot practically replicate).</p>



<p>It appears that the best you can do is <em>assume</em> the superclass will always use <code>NSCopyObject</code>, if it does currently, and just manually increment the retain count.  Like Objective-C with ARC, the language &amp; standard library really don&#8217;t want you to actually do this, but at least in Swift it&#8217;s relatively straightforward:</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)"><span role="button" tabindex="0" data-code="override func copy(with zone: NSZone? = nil) -&gt; Any {
    let result = super.copy(with: zone)
    
    if result.myProperty === self.myProperty {
        _ = Unmanaged.passRetained(myProperty)
    } else {
        result.myProperty = self.myProperty
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000"> </span><span style="color: #001080">zone</span><span style="color: #000000">: NSZone? = </span><span style="color: #0000FF">nil</span><span style="color: #000000">) -&gt; </span><span style="color: #267F99">Any</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: zone)</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> === </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #001080">_</span><span style="color: #000000"> = </span><span style="color: #267F99">Unmanaged</span><span style="color: #000000">.</span><span style="color: #795E26">passRetained</span><span style="color: #000000">(myProperty)</span></span>
<span class="line"><span style="color: #000000">    } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>The conditional <em>might</em> help protect you if the superclass stops using <code>NSCopyObject</code> in future &#8211; in that case, it&#8217;ll <em>probably</em> cause <code>myProperty</code> to default to nil (or to be assigned to some other instance, which you can discard), in which case you just want to assign to it normally.</p>



<p>In the interim &#8211; while <code>NSCopyObject</code> is in use, at least &#8211; the <code>myProperty</code> pointer will be copied verbatim and you have to assume it requires the extra, manual retain.  It&#8217;s <em>not</em> future-proof &#8211; it&#8217;s possible for the superclass to copy the pointer verbatim <em>and</em> increment the retain count for you &#8211; but at least in that case you &#8220;merely&#8221; get a memory leak, rather than a crash or memory corruption.</p>



<h2 class="wp-block-heading">Do as Apple says, not as Apple does</h2>



<p>The most frustrating part of all of this is that this is entirely Apple&#8217;s fault.  Sure, you can argue it&#8217;s not their fault that NeXT added this vile function to Foundation; that Apple &#8220;merely&#8221; inherited it and were &#8220;forced&#8221; to keep for backwards compatibility.  But it&#8217;s <em>entirely</em> Apple&#8217;s choice to have kept using it all this time, in their core frameworks, even while they&#8217;ve been telling everyone else to never use it.</p>



<p><code>NSCopyObject</code> has been a known problem-maker pretty much forever &#8211; it was a terrible idea right from the outset.  Blindly copying the bytes of an object instance, and just hoping that somehow that works correctly &#8211; in an <em>object-oriented</em> language derived from Smalltalk where <a href="https://developer.apple.com/documentation/foundation/nsnumber" data-wpel-link="external" target="_blank" rel="external noopener">even numbers are often reference types</a> &#8211; is farcical.</p>



<p>The introduction of ARC (in 2008) didn&#8217;t really help anything, as although it changed <code>NSCopyObject</code> to properly retain <em>ARC</em>-managed ivars, it did nothing for non-ARC-managed ivars (remember that ARC can be enabled in one library but not in another, and libraries can subclass each others&#8217; classes).</p>



<p><code>NSCopyObject</code> has been officially deprecated since 2012:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The NSCopyObject() function has been deprecated. It has always been a dangerous function to use, except in the implementation of copy methods<sup data-fn="19a637fd-3fbb-43d3-a9c0-29896c849e94" class="fn"><a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94" id="19a637fd-3fbb-43d3-a9c0-29896c849e94-link">2</a></sup>, and only then with care.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_8Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for OS X 10.8 Mountain Lion and iOS 6</a></cite></blockquote>



<p>…though Apple officially told everyone not to use it in 2008:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>This function is dangerous and very difficult to use correctly. It&#8217;s [sic] use as part of -copyWithZone: by any class that can be subclassed, is highly error prone. This function is known to fail for objects with embedded retain count ivars, singletons, and C++ ivars, and other circumstances.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_6Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for Mac OS X 10.6 Snow Leopard</a></cite></blockquote>



<p>And this was all still a decade or more after it was known that <code>NSCopyObject</code> was fundamentally evil, e.g. <a href="https://www.mulle-kybernetik.com/weblog/2004/argh_wasted_two_hours_on_stupi.html" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a>, and <a href="https://mail.gnu.org/archive/html/discuss-gnustep/2000-09/msg00097.html" data-wpel-link="external" target="_blank" rel="external noopener">GnuStep&#8217;s broken NSControl</a>.</p>



<p>And yet, Apple <em>still</em> use <code>NSCopyObject</code> themselves <em>to this very day</em>, in their own applications and frameworks &#8211; including major frameworks like AppKit that almost all 3rd party developers rely on.  <code>NSCell</code> is <em>still</em> broken, three decades later, as is <code>NSImage</code> &amp; <code>NSImageRep</code>, and <code>NSAnimation</code>.  Most of those are <em>explicitly designed to be subclassed</em>, despite Apple&#8217;s own very clear instructions to never mix subclassing with <code>NSCopyObject</code>.</p>



<p>Admittedly it&#8217;s not trivial for Apple to remove the <code>NSCopyObject</code> use &#8211; alas, <em>because</em> people have had to code myriad hacky workarounds to it, Apple now has to be careful not to break those workarounds.  That might even preclude fixing the existing code paths; it might require a <em>replacement</em> copy mechanism.  Which leads to…</p>



<h2 class="wp-block-heading">Tangent: NSCopying considered harmful</h2>



<p>The big driver of <code>NSCopyObject</code> use has long been <code><a href="https://developer.apple.com/documentation/foundation/nscopying" data-wpel-link="external" target="_blank" rel="external noopener">NSCopying</a></code>.  Classes that intend to be subclassed &#8211; but also semantically should support copying i.e. <code>NSCopying</code> &#8211; have long been making the mistake of thinking that means using <code>NSCopyObject</code>.  One need only read the NSCopying documentation, even <a href="https://preterhuman.net/macstuff/techpubs/macosx/System/Library/Frameworks/Foundation.framework/Versions/C/Resources/English.lproj/Documentation/Reference/ObjC_classic/Protocols/NSCopying.html" data-wpel-link="external" target="_blank" rel="external noopener">from before Mac OS X was even publicly released</a>, to see how dangerously fragile and error-prone <code>NSCopying</code> has always been.</p>



<p>Compounding the problem is that <code>NSCopying</code> <em>doesn&#8217;t work, by default, on subclasses</em>.  You <em>have</em> to override <code>copy(with:)</code> in every subclass<sup data-fn="712b988a-5cac-484c-9eaf-fc22bc3afc25" class="fn"><a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25" id="712b988a-5cac-484c-9eaf-fc22bc3afc25-link">3</a></sup>, but the compiler does not enforce this, because in Objective-C (and alas Swift) protocol conformance is <em>assumed</em> inherited even when it cannot correctly be without explicit, extra work by the subclass.</p>


<ol class="wp-block-footnotes"><li id="cf3bfb82-cbf6-40c4-817f-3092a63f4021">Yes, it&#8217;s still possible to this day to write Objective-C without using ARC &#8211; <code><a href="https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id8" data-wpel-link="external" target="_blank" rel="external noopener">-fno-objc-arc</a></code> / <code><a href="https://developer.apple.com/documentation/xcode/build-settings-reference#Objective-C-Automatic-Reference-Counting" data-wpel-link="external" target="_blank" rel="external noopener">CLANG_ENABLE_OBJC_ARC</a></code>.  There might even be valid (albeit unfortunate) use-cases for having to do so, such as for performance.<br><br>And even with ARC, it&#8217;s of course possible to have pointers to things which aren&#8217;t <code>NSObject</code>s and therefore aren&#8217;t handled by ARC, such as raw malloc allocations. <a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="19a637fd-3fbb-43d3-a9c0-29896c849e94">This is false and always has been (that it was safe to use in copy methods).  Apple&#8217;s false statements in the deprecation notices may ironically have caused even <em>more</em> instances of people using <code>NSCopyObject</code>. <a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="712b988a-5cac-484c-9eaf-fc22bc3afc25">Any and all that add retain-counted ivars, Swift stored properties, or ivars of C++ types that have destructors. <a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8281</post-id>	</item>
		<item>
		<title>When all you have is a Core Data, everything looks like…</title>
		<link>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/</link>
					<comments>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 24 Jun 2024 23:32:00 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Core Data]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[NSCoding]]></category>
		<category><![CDATA[Shark]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[SwiftData]]></category>
		<category><![CDATA[Time Profile]]></category>
		<category><![CDATA[XML]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8235</guid>

					<description><![CDATA[Reading SwiftData vs Realm: Performance Comparison reminded me of an anecdote from my days working on Shark, at Apple. I don&#8217;t really remember the timing &#8211; sometime between 2006 and 2010 &#8211; but presumably around 2006 as I recall it was when Core Data was still relatively new. For whatever reason, there was a huge&#8230; <a class="read-more-link" href="https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Reading <a href="https://www.emergetools.com/blog/posts/swiftdata-vs-realm-performance-comparison" data-wpel-link="external" target="_blank" rel="external noopener">SwiftData vs Realm: Performance Comparison</a> reminded me of an anecdote from my days working on <a href="https://leopard-adc.pepas.com/documentation/DeveloperTools/Conceptual/SharkUserGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005233-CH1-DontLinkElementID_6" data-wpel-link="external" target="_blank" rel="external noopener">Shark</a>, at Apple.</p>



<p>I don&#8217;t really remember the timing &#8211; sometime between 2006 and 2010 &#8211; but presumably around 2006 as I recall it was when <a href="https://en.wikipedia.org/wiki/Core_Data" data-wpel-link="external" target="_blank" rel="external noopener">Core Data</a> was still relatively new.  For whatever reason, there was a huge push internal to Apple to use Core Data <em>everywhere</em>.  People were running around all over the place asking &#8220;can it be made to use Core Data?&#8221;, for Apple&#8217;s frameworks and applications.</p>



<p>Keep in mind that Core Data at that time was similar to <a href="https://developer.apple.com/documentation/swiftdata" data-wpel-link="external" target="_blank" rel="external noopener">SwiftData</a> now &#8211; very limited functionality, and <em>chock full</em> of bugs.  But of course it&#8217;s the nature of &#8216;shiny&#8217; new things that their proponents think it&#8217;s the second coming and the cure for all ills.</p>



<p>So, I recall sitting down with a couple of folks from the Core Data team, that were there to see if Shark could adopt Core Data.  A little like letting the missionaries in, if only out of morbid curiosity.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1200" height="675" src="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif" alt="Still from the scene in Orgazmo with the Mormon Missionaries greeting a homeowner at their door and asking &quot;Have you heard the good news about Core Data?&quot;." class="wp-image-8240" srcset="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif 1200w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-256x144.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-1024x576.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-768x432.avif 768w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data@2x.avif 2400w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-256x144@2x.avif 512w" sizes="(max-width: 1200px) 100vw, 1200px" /></figure>



<p>Have you heard the good news?  Core Data is here to save your very data.  It&#8217;s effortless and divine and its unintuitive, thread-unsafe API will definitely not be the bane of all its users for the next fifteen years.</p>



<p>Jokes aside, they were in fact earnestly curious if Shark could use Core Data, instead of its own purpose-built binary formats, for storing &amp; querying its profiling data.  It was perhaps the classic case of naively underestimating the complexity of a foreign domain.  By my recollection, they assumed our profiling data was just a small handful of homogenous, relatively trivial records.  &#8220;At second N, the program ran the function named XYZ&#8221; or somesuch.</p>



<p>I think we (Shark engineers) tried to be open-minded and kind.  We were sceptical, but you never know until you actually look.  We could see some potential for a more general query capability, for example.  But of course the first and most obvious hurdle was: how well does Core Data handle sizeable numbers of records?  Oh yes, was the response, it&#8217;s great even with tens of thousands of records.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1128" height="480" src="https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot.avif" alt="Still image of the pawn shop scene from Star Trek IV (The Voyage Home) showing Kirk &amp; Spock responding to the offer of $100 for the antique spectacles with &quot;Is that a lot?&quot;." class="wp-image-8236" srcset="https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot.avif 1128w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-256x109.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-1024x436.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-768x327.avif 768w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot@2x.avif 2256w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-256x109@2x.avif 512w" sizes="(max-width: 1128px) 100vw, 1128px" /></figure>



<p>We asked how it did with tens of <em>millions</em> of records, and that was pretty much the end of the conversation.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Background on the Time Profile data structure</summary>
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>For context, the data in a Shark Time Profile (for example) was basically an array of samples, where each sample records the process &amp; thread IDs, and the callstack (expressed as an array of pointer-sized values; the first being the current PC and the rest the return addresses found by walking back up the thread&#8217;s stack).</p>



<p>Callstacks back then were relatively small, by modern standards &#8211; this was predominately C/C++/Objective-C code which tended to be far simpler in its structure than e.g. Swift; <em>way</em> fewer closures (blocks), no async suspension points to split logical functions up into numerous implementation functions, etc.  So the average was probably something in the low tens.  A hundred frames was considered a <em>big</em> callstack (which is sadly funny in hindsight, given that&#8217;s trivial by e.g. SwiftUI&#8217;s standards 😒).</p>



<p>A useful profile had at least thousands of such samples, and typical profiles were in the tens to hundreds of thousands (the latter usually for All Threads States profiles, particularly those of the whole system).  Some profiles could run into the millions or tens of millions (it&#8217;s not always easy or predictable as to when a performance problem will exhibit itself, so recording sometimes had to start early and run long).</p>



<p>I&#8217;m pretty sure Shark used <code><a href="https://developer.apple.com/documentation/foundation/nscoding" data-wpel-link="external" target="_blank" rel="external noopener">NSCoding</a></code> for the overall serdes, but a lot of that serdes was of huge chunks of (as far as <code>NSCoding</code> was concerned) arbitrary bytes. The file format was overall fairly efficient (though I don&#8217;t recall it ever using explicit data compression, nor even delta encoding for callstacks).</p>
</div></div>
</details>



<p>It wasn&#8217;t just the volume of data, it was also the dramatic difference in representation efficiency. The in-memory representation in Shark was basically as efficient as it could be &#8211; basically just arrays of compact structs, sometimes with pointers to other arrays (which might share a <code>malloc</code> block to avoid the overhead of small allocations) which were usually just of <code>uint32_t</code> or <code>uint64_t</code>. The most important operations &#8211; indexing to an arbitrary point in the profile&#8217;s timeline, then scanning forward over the data &#8211; were about as fast as they can possibly be.</p>



<p>In contrast, Core Data would have required an entire <em>object</em> (<code><a href="https://developer.apple.com/documentation/coredata/nsmanagedobject" data-wpel-link="external" target="_blank" rel="external noopener">NSManagedObject</a></code> subclass) for at least every sample, if not every <code>uintXX_t</code> in the callstack (depending on how &#8216;pure&#8217; you wanted the design to be). It would have increased memory usage by at least an order of magnitude &#8211; and Shark already struggled with big profiles on the hardware of the day, which typically had just a couple of GiB of RAM. Even the most trivial operations &#8211; like reading the data in from disk and iterating it sequentially would have been <em>thousands</em> of times slower.</p>



<p>In defence of the Core Data folks in the meeting &#8211; and I don&#8217;t remember who specifically it was &#8211; they never tried to misrepresent or exaggerate what Core Data could do.  I seem to recall them being quite nice people.  But as soon as we started explaining the type and volume of data that we worked with, they clearly gave up on any kind of pitch.  Core Data was designed for <em>developer convenience</em>, not runtime efficiency or performance.</p>



<p>It&#8217;s never ceased to surprise and disappoint me how many folks try to arbitrarily apply generalised data storage systems &#8211; <em>particularly</em> SQLite and MySQL, or wrappers thereover.  Usually for the same reasons &#8211; perceived convenience to them, right now, not necessarily efficiency (nor the convenience of their successors).</p>



<p>I guess by modern standards SQLite is considered efficient and fast, but &#8211; hah &#8211; <em>back in my day</em> SQLite was what you used when you didn&#8217;t have time to write your own, <em>efficient and fast</em> persistent data management system.</p>



<p>See also JSON and its older sister XML. 😔</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8235</post-id>	</item>
		<item>
		<title>Apple antitrust &#038; music piracy</title>
		<link>https://wadetregaskis.com/apple-antitrust-music-piracy/</link>
					<comments>https://wadetregaskis.com/apple-antitrust-music-piracy/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 21 Mar 2024 22:25:59 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[antitrust]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[entitlement]]></category>
		<category><![CDATA[music piracy]]></category>
		<category><![CDATA[Napster]]></category>
		<category><![CDATA[U.S. Department of Justice]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7895</guid>

					<description><![CDATA[The position of many folks &#8211; most recently the U.S. Department of Justice &#8211; against Apple has drawn an unexpected parallel to that of music pirates in the late 90&#8217;s and early 2000&#8217;s. If you&#8217;re too young to have personally lived that time, just know this key point: back then, buying or streaming music online&#8230; <a class="read-more-link" href="https://wadetregaskis.com/apple-antitrust-music-piracy/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>The position of many folks &#8211; <a href="https://www.justice.gov/opa/pr/justice-department-sues-apple-monopolizing-smartphone-markets" data-wpel-link="external" target="_blank" rel="external noopener">most recently the U.S. Department of Justice</a> &#8211; against Apple has drawn an unexpected parallel to that of music pirates in the late 90&#8217;s and early 2000&#8217;s.</p>



<p>If you&#8217;re too young to have personally lived that time, just know this key point: back then, buying or streaming music online was basically not a thing<sup data-fn="c69e16df-1a11-4f0e-8a4f-6da2431f49d8" class="fn"><a href="#c69e16df-1a11-4f0e-8a4f-6da2431f49d8" id="c69e16df-1a11-4f0e-8a4f-6da2431f49d8-link">1</a></sup>.  You bought CDs in physical stores, or <em>maybe</em> by mail order from an online retailer if you were on the bleeding edge.  Or you listened to the radio.  Or, you traded pirated copies of CD rips via a plethora of file sharing apps, most famously <a href="https://en.wikipedia.org/wiki/Napster" data-wpel-link="external" target="_blank" rel="external noopener">Napster</a>.</p>



<p>It was the opinion of quite a few people &#8211; my younger, more ignorant self among them &#8211; that if the music industry wasn&#8217;t willing to provide us the songs we wanted by the means we wanted and at the price we wanted, then it was their fault that we pirated their product.</p>



<p>Talk about entitlement.</p>



<p>Computer game pirates had &#8211; and seemingly still have &#8211; a similar attitude.  &#8220;$70 for a game?  Outrageous!  I have no choice but to pirate it!&#8221;</p>



<p>Bullshit.  You have plenty of choice: you can pay for it, or you can not have it.  You&#8217;re not <em>entitled</em> to the work of others.  <a href="https://en.wikipedia.org/wiki/Slavery" data-wpel-link="external" target="_blank" rel="external noopener">That used to be believed</a>, but not anymore.</p>



<p>A similar sense of entitlement seems to underpin many critiques of Apple&#8217;s business practices.  While I&#8217;m no fan of some of Apple&#8217;s business practices &#8211; because it&#8217;s always a shame to see flaws in an otherwise good or promising product &#8211; I&#8217;m under no delusion that I&#8217;m somehow entitled to have Apple&#8217;s products at all, let-alone on purely my own terms.</p>


<ol class="wp-block-footnotes"><li id="c69e16df-1a11-4f0e-8a4f-6da2431f49d8">I think it&#8217;s now largely forgotten just how <em>big</em> a deal it was when the <a href="https://en.wikipedia.org/wiki/ITunes_Store" data-wpel-link="external" target="_blank" rel="external noopener">iTunes Music Store</a> was announced in April 2003, with <em>five</em> of the biggest western music labels onboard. <a href="#c69e16df-1a11-4f0e-8a4f-6da2431f49d8-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/apple-antitrust-music-piracy/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7895</post-id>	</item>
		<item>
		<title>Apple Vision Pro first impressions</title>
		<link>https://wadetregaskis.com/apple-vision-pro-first-impressions/</link>
					<comments>https://wadetregaskis.com/apple-vision-pro-first-impressions/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 21 Feb 2024 02:32:02 +0000</pubDate>
				<category><![CDATA[Reviews]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Apple Vision Pro]]></category>
		<category><![CDATA[Tested]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7758</guid>

					<description><![CDATA[This morning I tested out Apple&#8217;s Vision Pro in an Apple Store. And I&#8217;ve decided to write about it, mostly for my own future nostalgia, but also because my experience was markedly different to what&#8217;s been widely reported in tech news. I had intended to just buy an Apple Vision Pro on release day, but&#8230; <a class="read-more-link" href="https://wadetregaskis.com/apple-vision-pro-first-impressions/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This morning I tested out Apple&#8217;s Vision Pro in an Apple Store.  And I&#8217;ve decided to write about it, mostly for my own future nostalgia, but also because my experience was markedly different to what&#8217;s been widely reported in tech news.</p>



<p>I had intended to just buy an Apple Vision Pro on release day, but by the time I woke up and went to Apple&#8217;s online store &#8211; a couple of hours after pre-orders opened &#8211; they were showing a nearly two month shipping delay (and no in-store pick-ups at all).  So I figured there was no point ordering then; like it or not I&#8217;d have plenty of time to see what others think first.</p>



<p>Of course, it turns out it was not in fact massively out of stock.  I later found out that people had ordered theirs later that day, or even the next day, and were still included in the very first shipment.  Nobody had to wait two months for theirs, even if they ordered weeks later.  Apple&#8217;s online store was full of shit.</p>



<p>But I&#8217;m glad for it, because as soon as I was resigned to not ordering one, I was at ease with that decision.  I felt oddly relieved.  Having since read &amp; listened to many people&#8217;s impressions &amp; commentary, over the last month, my confidence in that decision only increased.  And now that I&#8217;ve tried one for real, I am certain of it.</p>



<h2 class="wp-block-heading">The Good</h2>



<h3 class="wp-block-heading">Ease of use</h3>



<p>I found the interface to be pretty intuitive.  Eye tracking worked very well, and gesture recognition was fairly reliable &#8211; neither were perfect, but then mouse tracking and &#8220;the key I <em>meant</em> to press&#8221; tracking isn&#8217;t perfect either. 😉</p>



<p>It&#8217;s hard to say with any confidence from such a short use of the Vision Pro, but my impression is that its eye &amp; gesture tracking is at least as accurate as touch on iPhones, iPads, and Apple Watches.  My Apple Watch &amp; iPhone routinely reject my taps just to spite me (they animate the GUI to show that they <em>know</em> that I did tap, yet they refuse to accept it).</p>



<p>Which is to say, I suspect it would annoy me at times with its errors but I don&#8217;t think it&#8217;d be a barrier to long-term use.</p>



<h3 class="wp-block-heading">Sound</h3>



<p>One thing which I did not remotely anticipate is how good the sound isolation is &#8211; even more astounding given there&#8217;s nothing in or on your ear to provide a physical barrier.  There were dozens of people in the Apple Store, including several pairs right around me going through their own Vision Pro demos or iPhone upgrades, and with the headset on I barely registered any of their conversations.</p>



<p>I find it hard to believe it&#8217;s due to traditional noise cancellation methods, just given the physical position and arrangement of the speakers, so this might be as much a psychological &#8216;trick&#8217; as anything.  In any case, it is effective.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="3206" height="1186" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view.avif" alt="" class="wp-image-7770" srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view.avif 3206w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view-1024x379.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view-2048x758.avif 2048w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view-256x95.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Profile-profile-view-256x95@2x.avif 512w" sizes="auto, (max-width: 3206px) 100vw, 3206px" /><figcaption class="wp-element-caption">That&#8217;s the tiny little speaker.  Yep, that tiny little slit.  It looks like it&#8217;s merely the vent or a microphone for noise cancellation, but that is the actual speaker &#8211; it sits a little above and forward of your ear, directing sound laterally along the side of your head, rather than directly into it.  It seems like weird placement until you realise that we humans are very good at hearing things directly in front of us, despite our ears being on the sides.  Somehow, the system works.</figcaption></figure>
</div>


<p>I was also impressed by how well 3D audio worked (and how good the sound quality was in general).  Better than AirPods (and Beats Pros) in my experience, although that&#8217;s a low bar.</p>



<p>I&#8217;m less sure how the Vision Pro&#8217;s speakers stack up against real headphones &#8211; even my aging Sony MDR-ZX780DCs &#8211; but it&#8217;s at least a reasonable comparison, which is impressive given the Vision Pro&#8217;s speaker&#8217;s form factor.</p>



<h3 class="wp-block-heading">Real world view</h3>



<p>When the Vision Pro was first announced I was a little disappointed that it uses cameras to &#8216;fake&#8217; transparency, rather than using genuine optical transparency.  Nonetheless, I was happy to see that the effect is mostly sufficient.</p>



<p>It wasn&#8217;t hard to find flaws if you looked &#8211; the cameras are not correctly placed to actually see what you see, for example, so any objects closer than about a metre have noticeable parallax errors.  That&#8217;s very noticeable if you do something as simple as move your hands in front of you, even at arms length.</p>



<p>Yet, looking around the real world worked fine in practice.  There is perceptible lag, but only <em>barely</em> &#8211; not enough to really cause any issues; you&#8217;re not going to accidentally walk into moving objects, for example, and you could probably even play [real] sports with the Vision Pro on (although you wouldn&#8217;t be doing yourself any favours).</p>



<p>I should note that it was in no way <em>realistic</em> because you&#8217;re clearly looking at a resolution-limited computer screen (more on that later).  And I didn&#8217;t even test things like dynamic range or optical aberrations of the lenses, as I was in a very evenly lit and low-contrast Apple Store.</p>



<h3 class="wp-block-heading">Immersive experiences</h3>



<p>I didn&#8217;t get to do the butterfly &amp; dinosaur one, which is a shame because by all accounts it&#8217;s particularly good, but I got the quick demo reel of spherical videos (along with a couple of &#8220;3D&#8221; photos &amp; videos).  It was a bit hit &amp; miss (more on that in later sections), but there were a few moments where I was actually pretty pleased with the experience.  I&#8217;ve visited Haleakalā a few times but never seen the crater &#8211; the weather has always conspired against me &#8211; so I actually got lost for a minute or so just enjoying that view.  The subtle animation of the mist drifting up &amp; down the crater walls was a sublime touch.  Likewise the gentle rain on the lake near Mount Hood.</p>



<p>The 3D video of a kid blowing out the candles on their birthday cake worked relatively well.  The feeling of depth was nice &#8211; aided by the cake being very close to the camera and the use of a wide angle lens.  I suspect wide-angle photos are much more amenable to the &#8220;3D photo&#8221; effect (they&#8217;re often described as more immersive even in plain 2D &#8211; although I think that effect is exaggerated by many people).</p>



<figure class="wp-block-video aligncenter"><video controls src="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-3D-birthday-cake-movie.mp4"></video><figcaption class="wp-element-caption">This is the video I&#8217;m talking about (above), but note that it feels here <em>nothing</em> like it does in the Vision Pro, where it fills your field of view and is much closer to being there in person.  Though still not fooling anyone into thinking it&#8217;s reality, to be clear.</figcaption></figure>



<h3 class="wp-block-heading">Human intrusions</h3>



<p>The way people very subtly appear when you look in their direction while in full VR mode was quite nice.  I like that they are visible but remain faint apparitions.  I had received the mistaken impression from second-hand accounts that intruders appeared largely opaquely; much more obtrusively.  The implementation strikes a good balance in providing awareness &#8211; and facilitating communication &#8211; without interrupting more than necessary.</p>



<h3 class="wp-block-heading">Physical comfort</h3>



<p>It&#8217;s of course very hard to deduce the real-world comfort of the Vision Pro from a mere fifteen minute session, but for whatever it&#8217;s worth I didn&#8217;t have any issues regarding weight, size, or contact.  The headset certainly wasn&#8217;t unnoticeable, but I had no issues forgetting about those aspects while using it.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="3580" height="2022" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery.avif" alt="" class="wp-image-7764" srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery.avif 3580w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery-1024x578.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery-2048x1157.avif 2048w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery-256x145.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-with-battery-256x145@2x.avif 512w" sizes="auto, (max-width: 3580px) 100vw, 3580px" /></figure>
</div>


<p>Also, it wasn&#8217;t until I was mostly done writing this article that I even remembered that the Vision Pro has a cable sticking out of it.  The battery pack stayed on the table in front of me, and the only time I noticed the cable &#8211; even in the slightest &#8211; was when putting the unit on (merely because I had to make sure the cable wasn&#8217;t tangled around my head).  I was expecting to feel the cable tugging and pushing on the headset during use, but did not.</p>



<p>This is in stark contrast to the power cable on my MacBook Air which is <em>constantly</em> getting snagged and yanked by my apparently villainous couch.  So I&#8217;m very curious how the Vision Pro would work in a more typical environment, rather than at the Apple Store with basically nothing but empty space around me as I sat on a stool.</p>



<h3 class="wp-block-heading">Minimal internal lens flare &amp; reflection</h3>



<p>At least in the bright Apple Store, I didn&#8217;t find the lens flare &amp; reflections to be distracting.  They are present, but I really only noticed them on the very first screen, where you&#8217;re in a black void and thus of course any such optical imperfections are most visible.</p>



<p>This is pretty good, by my estimation, since I find sunglasses to be irritating due to seeing the reflection of my own eyeballs in them.  The Vision Pro has the big benefit of the [mostly] enclosed mask, to largely eliminate external sources of light.  So it <em>should</em> have far fewer issues with flare &amp; reflections, as a matter of principle.</p>



<p>The main reflection I did notice &#8211; and had a slightly harder time ignoring &#8211; was the glow of the screen on the inside of the light shield.  It&#8217;s a pity Apple used a grey, textured material for the interior, rather than something dark like <a href="https://www.the-black-market.com/marketplace/mb-fabric-kiwami/" data-wpel-link="external" target="_blank" rel="external noopener">Musou black fabric</a>.</p>



<p>Still, unless you plan to work in a very dark or high-contrast VR environment &#8211; perhaps the moonscape that I tried briefly during the demo &#8211; you should be fine in this respect.</p>



<h3 class="wp-block-heading">Real world geometry is no limit</h3>



<p>Folks have unanimously stated that &#8211; surprising or not &#8211; there&#8217;s no visual incongruence with pushing AR elements &#8220;through&#8221; real-world ones.  One of the first tests I did was to grab a window and shove it through the table in front of me.  Despite clearly violating all logic, my eyes &amp; brain apparently had no issue with it &#8211; it didn&#8217;t feel wrong, or weird, or uncomfortable.</p>



<p>So &#8211; while I didn&#8217;t test it &#8211; I can easily believe that you can indeed watch a movie on a [virtually] cinema-sized screen despite being stuck in cattle class on a plane.  And I can imagine it would be a genuine emotional benefit to have that feeling of so much more personal space, even though it&#8217;s &#8220;fake&#8221;.</p>



<p>Honestly, if I were to get a Vision Pro its use as an aid to commercial air travel might actually be one of the most justifiable reasons.</p>



<h2 class="wp-block-heading">The Bad</h2>



<h3 class="wp-block-heading">Buggy demo units</h3>



<p>My time actually using the Vision Pro was significantly shortened by the demo unit refusing to reset properly.  It took three attempts &#8211; and a consultation between Apple Store staff &#8211; before they finally got it to work properly.</p>



<p>On the first two usage attempts it went straight from the &#8220;hold down the crown button to shift the lenses&#8221; to the home screen, bypassing eye tracking calibration.  It didn&#8217;t seem particularly unusable without proper calibration, but my Apple handler refused to start the demo without it.</p>



<h3 class="wp-block-heading">Bad light seal</h3>



<p>Perhaps it&#8217;s just me and my apparently tiny nose, but there was a <em>big</em> gap between the unit and my nose.  I could easily look through and see the table in front of me, my own lap, etc.  This surprised me given Apple themselves have put a lot of emphasis on the importance of a good fit and no light leaks in order to have the proper experience.</p>



<p>However, in use I didn&#8217;t find it much of an issue &#8211; I wasn&#8217;t distracted nor blinded by the light leaking in.  I think as long as my focus was actually on the screens of the Vision Pro, and my attention on whatever I was looking at within them, it was okay.  Although, just like the glare &amp; reflections within the lenses, it may not be so easy to ignore in a dark VR environment.</p>



<h3 class="wp-block-heading">Non-immersive experiences</h3>



<p>Most of the 3D photos &amp; videos I saw didn&#8217;t do much for me.  I can clearly see the appeal in theory, but the implementation on the Vision Pro is frustrated by several factors.</p>



<p>For a start a lot of the 3D photos &#8211; and especially videos &#8211; were blurry outside of the centre<sup data-fn="b2c66feb-281e-47f6-ba42-380f87bf438f" class="fn"><a href="#b2c66feb-281e-47f6-ba42-380f87bf438f" id="b2c66feb-281e-47f6-ba42-380f87bf438f-link">1</a></sup>.  <em>Really</em> blurry, in the case of the Alicia Keys clip &#8211; she was reasonably in focus but almost everything else was <em>way</em> out of focus.  I think in the original recording, although I guess I can&#8217;t be sure that it wasn&#8217;t buggy <a href="https://en.wikipedia.org/wiki/Foveated_rendering" data-wpel-link="external" target="_blank" rel="external noopener">foveated rendering</a> or somesuch.</p>



<p>Second, even when they weren&#8217;t blurry, they were often low resolution.  Hard to say whether this is because of the Vision Pro itself (more on this later) or with the source materials.</p>



<p>I was hoping that the panoramas would be a big (pleasant) experience, as I&#8217;m quite a fan of taking panoramic photos (and photo spheres) even though you can&#8217;t really view them well on fixed displays.  In a way I&#8217;ve been waiting and preparing for VR goggles for decades.  Heck, I was super excited when <a href="https://en.wikipedia.org/wiki/QuickTime_VR" data-wpel-link="external" target="_blank" rel="external noopener">Quicktime VR</a> was released, even though it turned out to unfortunately be decades ahead of its time.</p>



<p>Yet, I was a bit disappointed with the panorama experience on the Vision Pro.  I&#8217;m not entirely sure why, although I know one obvious reason was that you can see the edges.  Any illusion of being there is rattled when the edge of the photo appears in view.  Photo spheres are the better way to go.</p>



<p>Seeing the edges might sound trite &#8211; after all, we see the edges of photos and videos <em>all the time</em> on our existing displays; what&#8217;s the problem?  It&#8217;s a good question &#8211; I don&#8217;t know if I can explain it, I just know what I felt.  Perhaps it&#8217;s an uncanny valley sort of problem &#8211; because you <em>are</em> in a nominally immersive, VR environment, it matters so much more when the illusion is shattered.  Perhaps it&#8217;s something you get used to?</p>



<p>In any case, even for photo spheres the feeling of immersion is compromised by the field of view being way too small…</p>



<h3 class="wp-block-heading">Limited field of view</h3>



<p>I had heard that the Vision Pro has a limited field of view &#8211; even compared to its contemporaries &#8211; but I was still surprised to see what that&#8217;s actually like.  It&#8217;s even more limited than I expected.  It&#8217;s like wearing bad goggles &#8211; <a href="https://upload.wikimedia.org/wikipedia/commons/2/28/Horses_2.jpg" data-wpel-link="external" target="_blank" rel="external noopener">blinkers</a>, perhaps.  A kind of tunnel vision.  I&#8217;ve heard it said that you &#8220;just&#8221; need to keep your eyes centred and move only your head instead, but even looking dead ahead I clearly perceived the outline of the screens, and it took some effort to ignore that (mostly successfully).</p>



<p>As noted earlier, this kinda ruined any would-be feeling of immersion for me, in most cases.  You&#8217;re not much more &#8220;there&#8221; than you are viewing a photo in a picture frame (or on a traditional screen), or watching a video on a TV placed too far away.</p>



<h3 class="wp-block-heading">Everything&#8217;s too close &amp; big by default</h3>



<p>I was surprised that windows open way too close (in perceived depth) and too big (in field of view) by default.  I was constantly manually resizing things and pushing them away from me, in order to actually be able to see them [fully] and comfortably.</p>



<p>I do not like having to move my head just to look around a single window, it turns out.</p>



<p>I also found it quite unintuitive that windows enlarge as you push them away, maintaining the same angle of view.  It was not only annoying &#8211; since the whole point of pushing them away was to make them smaller and feel less claustrophobic &#8211; but it made it hard to actually judge if &amp; to what degree they were moving.  More than once I repeated a &#8220;get back&#8221; window movement because it seemed like the first try was silently ignored (and honestly, I can&#8217;t be sure it wasn&#8217;t &#8211; that&#8217;s the point).</p>



<h3 class="wp-block-heading">3D movies</h3>



<p>I watched the brief trailer for Super Mario Bros.  I didn&#8217;t really get much of a 3D effect, but I did get the very limited field of view (see prior point) and poor visual quality (see below).  I&#8217;m actually a bit bullish on 3D movies, but I think we&#8217;re still just not there technologically<sup data-fn="c293c2c5-07cc-474b-93da-d22dc1847861" class="fn"><a href="#c293c2c5-07cc-474b-93da-d22dc1847861" id="c293c2c5-07cc-474b-93da-d22dc1847861-link">2</a></sup>.</p>



<p>I <em>do</em> like to have a big screen &#8211; such that my vision is pretty filled by a movie &#8211; but apparently I really don&#8217;t like it when there&#8217;s a black mask over the outskirts of the movie.  Even if it&#8217;s only visible in my peripheral vision.</p>



<p>Possibly I&#8217;d get used to this in time, but of course I don&#8217;t really want to &#8211; I <em>should</em> be able to see the whole movie.  Otherwise, what&#8217;s the point?  If the director wanted me to see only a subset of the view, they&#8217;d have filmed it that way.</p>



<p>I presumably could also have manually moved the window back &#8211; and I did for some of the other videos I watched &#8211; but it&#8217;s just not practical to have to do that for every video I ever watch.</p>



<p>Perhaps the problem is in taking existing movies &#8211; designed for a relatively tiny field of view (≤40° typically) &#8211; and naively shoving them in your face in the name of immersion.  Maybe what we need is to add <em>new</em> content <em>around</em> the existing frame.  Despite our huge angle of vision, our focus area is actually quite small.  It&#8217;s uncomfortable and confusing to have to look around frequently and rapidly just to make sense of a movie.</p>



<h2 class="wp-block-heading">The Ugly</h2>



<h3 class="wp-block-heading">Very blurry &amp; low resolution</h3>



<p>I was really surprised to see pixels.  Immediately.  Even though I was already aware &#8211; mainly from <a href="https://atp.fm/573" data-wpel-link="external" target="_blank" rel="external noopener">John&#8217;s stumbling</a> <a href="https://atp.fm/574" data-wpel-link="external" target="_blank" rel="external noopener">over the topic</a> on <a href="https://atp.fm" data-wpel-link="external" target="_blank" rel="external noopener">ATP</a> &#8211; that the PPD (Pixels per Degree) is actually quite poor on the Vision Pro, at just <a href="https://www.ifixit.com/News/90409/vision-pro-teardown-part-2-whats-the-display-resolution" data-wpel-link="external" target="_blank" rel="external noopener">34</a><sup data-fn="e5a18ecf-b94b-4d63-94e7-a27f928a4fe8" class="fn"><a href="#e5a18ecf-b94b-4d63-94e7-a27f928a4fe8" id="e5a18ecf-b94b-4d63-94e7-a27f928a4fe8-link">3</a></sup>.  A &#8220;Retina display&#8221; at typical viewing distances is around 100.  Even an ancient non-Retina Apple display is about 50.</p>



<p>In fact, 34 is about the same as an original Macintosh from 1984. 😳</p>



<p>I found <a href="https://qasimk.io/screen-ppd/" data-wpel-link="external" target="_blank" rel="external noopener">this handy calculator for determining PPD</a>, in case you want to estimate for your own devices.  If you have a 5k 27&#8243; monitor, for example, but typically sit with your nose about 4cm from it, then you&#8217;re already used to the Vision Pro&#8217;s display resolution<sup data-fn="0c1a4326-2fd3-433f-ad6b-0760ed38eeaf" class="fn"><a href="#0c1a4326-2fd3-433f-ad6b-0760ed38eeaf" id="0c1a4326-2fd3-433f-ad6b-0760ed38eeaf-link">4</a></sup>.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>For reference, <a href="https://en.wikipedia.org/wiki/Visual_acuity#Physiology" data-wpel-link="external" target="_blank" rel="external noopener">the human eye is apparently limited to about 128 PPD at best</a>.  That seems plausible just based on my own experience &#8211; I can&#8217;t really <em>see</em> individual pixels at ~100 PPD with my iMac Pro, for example, but my eyesight&#8217;s not perfect and that 128 number assumes absolute best-case conditions for distinguishing detail, which perhaps isn&#8217;t the typical reality.</p>
</div></div>



<p>Furthermore, unlike the original Macintosh&#8217;s screen &#8211; which at least had quite crisp pixels &#8211; the Vision Pro is blurry as well.  That surprised me less &#8211; I figured there might be some calibration required, which was perhaps unintentionally skipped by the buggy demo unit.  But my Apple Store handler didn&#8217;t seem to think so, yet seemed surprised by my comments (that the view was pixelated and blurry).  He had no real answer to that.  He implied (by omission) that my experience was normal. 😕</p>



<h3 class="wp-block-heading">Jittery</h3>



<p>The other thing I noticed within a literal second of using the unit is that the <em>artificial</em> visuals jitter &#8211; jump randomly about by a pixel or two.  All the time.  It&#8217;s less noticeable in full VR mode where you have no objective reference in the form of real world objects, but in AR mode everything displayed by the Vision Pro is shaking.  I was able to mostly ignore it throughout the demo &#8211; but only with conscious effort.  Any time I let my mind or vision wander in the slightest, the shaking immediately bothered me once more.</p>



<p>To be clear, this was while sitting perfectly still.  I didn&#8217;t really test actual lateral movement of the headset, as I was asked to stay seated for the entire demo.</p>



<p>Since everything you see in the Vision Pro is technically artificial &#8211; it&#8217;s all from opaque LED screens, even the view of the real world that&#8217;s piped in view cameras &#8211; this jitteriness is baffling.  I&#8217;m pretty sure it was the virtual objects that were moving, not the feed from the real world &#8211; just based on my own perception of what was and wasn&#8217;t moving &#8211; but I have no explanation for why that would be the case.  While I could sense a tiny bit of a lag in the real world view, it seemed to remain correctly positioned (and <em>stably</em> positioned, more to the point) relative to objective reality.</p>



<p>It&#8217;s hard to say from just fifteen minutes of use, but I suspect this jittering would contribute significantly to eye strain.</p>



<h3 class="wp-block-heading">Eye strain</h3>



<p>When I took the Vision Pro off, my eyes were immediately assaulted by [comparatively] bright light and much sharper everything.  It was both uncomfortable and a relief.</p>



<p>It took several minutes for my vision to de-blur.  It&#8217;s a very similar experience to looking through the viewfinder on my <a href="https://www.nikonusa.com/p/z-9/1669" data-wpel-link="external" target="_blank" rel="external noopener">Z9</a> for an extended period.  And similarly it took a good couple of hours for my eyes to get fully back to normal, and to stop feeling strained.</p>



<p>Based on that parallel experience, it seems <em>very</em> clear that I cannot use a Vision Pro for any significant amount of time, without serious eye strain that causes lasting blurry vision and headaches. 😣</p>



<p>I&#8217;m assured by my optometrist that my vision is actually excellent &#8211; much better than average for my age, and I&#8217;m not <em>that</em> old yet anyway &#8211; and I&#8217;ve never needed glasses for anything<sup data-fn="7ce3e963-6c60-435d-b877-3756cec415c8" class="fn"><a href="#7ce3e963-6c60-435d-b877-3756cec415c8" id="7ce3e963-6c60-435d-b877-3756cec415c8-link">5</a></sup>.  So I don&#8217;t think the problem is me.  I think the problem is quite apparent from the facts: the Vision Pro simply has a very low-resolution, unstable display that is uncomfortable to look at.</p>



<h2 class="wp-block-heading">So now what?</h2>



<p>Now I wait.</p>



<p>If I&#8217;d decided to develop apps for the Vision Pro, as a business choice, then it&#8217;d make sense to own one &#8211; it could be considered a dev kit; an early prototype.  But for now I&#8217;m content not to.  There doesn&#8217;t seem to be a rush &#8211; the app market is comparatively tiny.</p>



<p>For personal use it makes no sense to get a Vision Pro.  Aside from its novelty factor and some very limited uses, it&#8217;s inferior to a Mac for almost all purposes (like productivity tasks, watching movies, reading books, video calls, etc).</p>



<p>Yet I&#8217;m hopeful that the AR and VR headsets&#8217; times will come, eventually.  Still at least several years from now, based on what I&#8217;m seeing with the Vision Pro.  But one day.</p>



<p>I&#8217;ve been trying to discern if the Vision Pro is an iPhone moment or an iPad moment, since it was announced.  I was bearish on the iPhone and bullish on the iPad, apparently at odds with the entire rest of the planet.  And I&#8217;ve since conceded defeat entirely on the iPad &#8211; when I replaced my iPad Pro 13&#8243; with a MacBook Air, it was such a <em>relief</em> &#8211; I finally had a device which I could use freely and broadly, rather than a glorified iPhone for web browsing and watching video.  So I&#8217;m leery about getting too enthusiastic about a new class of device which clearly has some of the same existential challenges as the iPad.</p>



<p>One thing seems clear &#8211; it&#8217;s not a Mac moment.  I don&#8217;t see anything with the Vision Pro that fundamentally changes the nature of computing, let-alone day-to-day life.  But then, I don&#8217;t think it&#8217;s fair to expect it to &#8211; there&#8217;s only been one Mac moment so far.</p>


<ol class="wp-block-footnotes"><li id="b2c66feb-281e-47f6-ba42-380f87bf438f">This might be just flaws in the demo material, although it&#8217;s hard to imagine Apple letting that fly.  I have to assume the Vision Pro itself is the limitation here.<br><br>That said, in the &#8220;3D&#8221; video of the girl popping bubbles I saw what looked exactly like JPEG or MPEG compression artefacts (mainly blocking and over-smoothing).  I can&#8217;t be certain whether that&#8217;s an issue in the video file or in its playback by the Vision Pro, but I would assume that graphical rendering glitches would appear somewhat differently…? <a href="#b2c66feb-281e-47f6-ba42-380f87bf438f-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="c293c2c5-07cc-474b-93da-d22dc1847861">Most immediately, we lack the display technology to show a 4K (or better) video in 3D, whether via a headset like the Vision Pro or traditional means like coloured or polarised glasses.  But that&#8217;s merely the easy part &#8211; we also need real-time rendering of the movie itself, in order to do proper foveated depth rendering &#8211; i.e. so that if you look at something in the foreground or background you can actually bring it into focus, even if the director hadn&#8217;t planned on that.  As best I can tell practically nobody is even working on that yet. 😕 <a href="#c293c2c5-07cc-474b-93da-d22dc1847861-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="e5a18ecf-b94b-4d63-94e7-a27f928a4fe8">Tangentially, it&#8217;s baffling how news media outlets like <a href="https://www.theverge.com/2024/2/7/24064558/apple-vision-pro-pixel-density-ifixit-teardown" data-wpel-link="external" target="_blank" rel="external noopener">The Verge can shamelessly try to spin that as <em>in any way good</em></a>.  Talk about drinking the Apple koolaid. 😠 <a href="#e5a18ecf-b94b-4d63-94e7-a27f928a4fe8-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="0c1a4326-2fd3-433f-ad6b-0760ed38eeaf">And if you can actually focus at that distance, then I applaud &amp; hate you, because I can only reminisce fondly about those days.  Stupid aging. 😔 <a href="#0c1a4326-2fd3-433f-ad6b-0760ed38eeaf-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="7ce3e963-6c60-435d-b877-3756cec415c8">Although admittedly I can no longer focus on the end of my nose like I used to be able to do when I was a few years younger, so I know my descent into old-age far-sightedness has begun; I&#8217;m told I will need reading glasses at some point in my life (barring an unfortunate early exit). <a href="#7ce3e963-6c60-435d-b877-3756cec415c8-link" aria-label="Jump to footnote reference 5">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/apple-vision-pro-first-impressions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-3D-birthday-cake-movie.mp4" length="13687254" type="video/mp4" />

			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/02/Apple-Vision-Pro-2048x1123.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7758</post-id>	</item>
		<item>
		<title>Proactive Peek &#038; Reveal on Edge Hover</title>
		<link>https://wadetregaskis.com/proactive-peek-reveal-on-edge-hover/</link>
					<comments>https://wadetregaskis.com/proactive-peek-reveal-on-edge-hover/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 12 Feb 2024 22:29:57 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Proactive Peek]]></category>
		<category><![CDATA[Reveal on Edge Hover]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[User Defaults]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7692</guid>

					<description><![CDATA[These are two misfeatures that appeared in macOS Sonoma (I believe). They are where a closed sidebar forces its way back into view temporarily, if the mouse comes to rest near the relevant edge of the window. It&#8217;s easy to see how some UI designer thought this was a good idea. Surely if you move&#8230; <a class="read-more-link" href="https://wadetregaskis.com/proactive-peek-reveal-on-edge-hover/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>These are two misfeatures that appeared in macOS Sonoma (I believe).  They are where a closed sidebar forces its way back into view temporarily, if the mouse comes to rest near the relevant edge of the window.</p>



<figure class="wp-block-video aligncenter fucking-wordpress"><video height="282" style="aspect-ratio: 174 / 282;" width="174" autoplay loop preload="auto" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Proactive-Peek.mp4" playsinline></video></figure>



<p>It&#8217;s easy to see how some UI designer thought this was a good idea.  Surely if you move the mouse near the edge of the window (or the screen, in fullscreen mode) and rest it there, it&#8217;s because you&#8217;re looking forlornly for your lost sidebar?  What could be more helpful and delightful than your missing sidebar popping into view?!</p>



<p>Unfortunately, they have ignored that fact that there is usually already other GUI controls at the edge of the window, not the least of which being the window edge itself (for drag-resizing of the window).  Scrollbars are another common inhabitant of window edges.</p>



<p>&#8220;Proactive Peek&#8221; is the worst of these two because not only does it change what&#8217;s under the mouse cursor <em>just</em> as you&#8217;re likely to click, stealing the click away from its true target, but it actually shrinks the window&#8217;s visible contents.  This leads to layout changes and motion noise, particularly in web pages where it can have knock-on effects like mucking with the scroll position or causing major changes by crossing some &#8220;responsive design&#8221; threshold.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>I&#8217;m <a href="https://forums.macrumors.com/threads/annoying-window-resizing-when-cursor-is-on-the-left.2408523/" data-wpel-link="external" target="_blank" rel="external noopener">not</a> <a href="https://www.reddit.com/r/MacOS/comments/12x96sg/disable_window_resize_when_moving_mouse_pointer/?rdt=34945" data-wpel-link="external" target="_blank" rel="external noopener">the</a> <a href="https://www.reddit.com/r/MacOS/comments/17ep9sr/when_i_rest_my_mouse_on_the_left_edge_of_safari/" data-wpel-link="external" target="_blank" rel="external noopener">only</a> <a href="https://mastodon.social/@stroughtonsmith/111914161132274876" data-wpel-link="external" target="_blank" rel="external noopener">one</a> to detest this &#8216;feature&#8217;, although it&#8217;s hard to know the sentiments of the overall Mac community since these &#8216;features&#8217; have no official names &#8211; I deduced them from the private method &amp; category names in AppKit &#8211; so they&#8217;re hard to search for.  FWIW, I could not find a single positive comment about these behaviours.</p>
</div></div>



<p>Unfortunately there&#8217;s no way to turn this poorly-considered &#8216;feature&#8217; off completely, although you can effectively disable the &#8220;Reveal on Edge Hover&#8221; piece:</p>



<figure class="wp-block-pullquote"><blockquote><p><code>defaults write -g NSSplitViewItemFullscreenEdgeRevealDelay -float 1e300</code><br><code>defaults write -g NSSplitViewItemTileEdgeRevealDelay -float 1e300</code></p><cite><a href="https://mastodon.social/@stroughtonsmith/111914177378857069" data-wpel-link="external" target="_blank" rel="external noopener">Steve Troughton-Smith</a></cite></blockquote></figure>



<p>If you&#8217;re an app developer it looks like (I haven&#8217;t tested it) you can disable these &#8216;features&#8217; within your own app, at least, by implementing the private <code>NSSplitView</code> delegate method <code>_splitView:canProactivePeekArrangedView:</code> and setting the <code>NSSplitView</code> <code>revealsOnEdgeHoverInFullscreen</code> property to <code>NO</code> (or the <code>NSSplitViewController</code> private property <code>&nbsp;_hasItemToRevealOnEdgeHover</code> to <code>NO</code>, if you&#8217;re using <code>NSSplitViewController</code>).  Or subclassing <code>NSSplitView</code> and overriding <code>_canDoSidebarProactivePeek</code> and <code>_canDoInspectorProactivePeek</code> to return NO &#8211; though that only applies to Proactive Peek.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/proactive-peek-reveal-on-edge-hover/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://wadetregaskis.com/wp-content/uploads/2024/02/Proactive-Peek.mp4" length="34788" type="video/mp4" />

			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/02/Proactive-Peek.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7692</post-id>	</item>
		<item>
		<title>A perfect little feature: Universal Clipboard</title>
		<link>https://wadetregaskis.com/a-perfect-little-feature-universal-clipboard/</link>
					<comments>https://wadetregaskis.com/a-perfect-little-feature-universal-clipboard/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 01 Feb 2024 19:27:48 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[AirDrop]]></category>
		<category><![CDATA[AirPlay]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Apple Watch Unlock]]></category>
		<category><![CDATA[Continuity]]></category>
		<category><![CDATA[Happy]]></category>
		<category><![CDATA[SMS Forwarding]]></category>
		<category><![CDATA[Universal Clipboard]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7639</guid>

					<description><![CDATA[Perhaps in karmic balance of my previous post critical of one of Apple&#8217;s APIs, I want to highlight an Apple OS feature that I use every day, that ironically is so simple, obvious, and reliable that I almost never stop to appreciate it. Universal Clipboard basically just means you can copy something on one device&#8230; <a class="read-more-link" href="https://wadetregaskis.com/a-perfect-little-feature-universal-clipboard/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Perhaps in karmic balance of <a href="https://wadetregaskis.com/bad-api-example-filemanagers-urlforinappropriateforcreate/" data-wpel-link="internal">my previous post</a> critical of one of Apple&#8217;s APIs, I want to highlight an Apple OS feature that I use every day, that ironically is so simple, obvious, and reliable that I almost never stop to appreciate it.</p>



<p><a href="https://support.apple.com/en-us/102430" data-wpel-link="external" target="_blank" rel="external noopener">Universal Clipboard</a> basically just means you can copy something on one device and paste it on another, as long as they&#8217;re physically near each other and signed into the same Apple ID.  <a href="https://www.youtube.com/watch?v=XSNqTgd9SXU" data-wpel-link="external" target="_blank" rel="external noopener">There is no step 3</a>.</p>



<p>It&#8217;s such a beautiful feature, in concept and execution.  Admittedly most of the time I use it merely to shuffle authentication codes from my iPhone to my Mac, but still &#8211; I love not having to temporarily memorise arbitrary numbers and then type them in manually.</p>



<p>I think what really elevates it to perfection is that <em>it works</em>.  It sounds like such a vapid point, but it really makes all the difference.  Nominally Universal Clipboard is just one of a family of similar features, under the <a href="https://support.apple.com/en-au/102426" data-wpel-link="external" target="_blank" rel="external noopener">Continuity</a> moniker, but it&#8217;s the only one that actually works reliably and predictably.  <em>The only one!</em></p>



<p>Many of the other Continuity features are similarly great in concept, but just don&#8217;t work reliably.  <a href="https://support.apple.com/en-us/102545" data-wpel-link="external" target="_blank" rel="external noopener">SMSes</a> sometimes don&#8217;t sync to my Macs.  <a href="https://support.apple.com/en-au/102661" data-wpel-link="external" target="_blank" rel="external noopener">AirPlay</a> sometimes just won&#8217;t connect, or the video quality is comically bad.  <a href="https://support.apple.com/en-us/102442" data-wpel-link="external" target="_blank" rel="external noopener">Unlocking my Macs with my Watch</a> is a guessing game as to whether it feels like working today.  <a href="https://support.apple.com/en-kz/guide/mac-help/mh35868/mac" data-wpel-link="external" target="_blank" rel="external noopener">AirDrop</a> is so fussy and unreliable that it actually gives me anxiety if I even think of using it.</p>



<p>I wish Apple&#8217;s products had more features like Universal Clipboard.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/a-perfect-little-feature-universal-clipboard/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/02/Universal-Clipboard.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7639</post-id>	</item>
		<item>
		<title>-fomit-frame-pointer</title>
		<link>https://wadetregaskis.com/fomit-frame-pointer/</link>
					<comments>https://wadetregaskis.com/fomit-frame-pointer/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 24 Jan 2024 22:30:30 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[-fomit-frame-pointer]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[backtracing]]></category>
		<category><![CDATA[frame pointers]]></category>
		<category><![CDATA[i386]]></category>
		<category><![CDATA[Instruments]]></category>
		<category><![CDATA[Intel Core Duo]]></category>
		<category><![CDATA[Merom]]></category>
		<category><![CDATA[Shark]]></category>
		<category><![CDATA[Swift Forums]]></category>
		<category><![CDATA[x86-64]]></category>
		<category><![CDATA[Yonah]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7536</guid>

					<description><![CDATA[This is an elaboration of a post I made in a Swift Forums thread, SE-0419: Swift Backtracing API. The question was raised whether an official Swift backtracer should try to support code that doesn&#8217;t use frame pointers. Which immediately raised the question &#8211; in my mind &#8211; of if anyone is still using the &#8220;optimisation&#8221;&#8230; <a class="read-more-link" href="https://wadetregaskis.com/fomit-frame-pointer/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is an elaboration of <a href="https://forums.swift.org/t/se-0419-swift-backtracing-api/69595/13" data-wpel-link="external" target="_blank" rel="external noopener">a post I made</a> in a Swift Forums thread, <a href="https://forums.swift.org/t/se-0419-swift-backtracing-api/69595" data-wpel-link="external" target="_blank" rel="external noopener">SE-0419: Swift Backtracing API</a>.</p>



<p>The question was raised whether an official Swift backtracer should try to support code that doesn&#8217;t use frame pointers.  Which immediately raised the question &#8211; in my mind &#8211; of if anyone is still using the &#8220;optimisation&#8221; of omitting frame pointers, anyway.  And perhaps more importantly, whether they <em>should</em> still be omitting frame pointers.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<h4 class="wp-block-heading">What is a frame pointer?</h4>



<p>A pointer to a stack frame, <em>held in a well-known location</em>.  That location can be in the stack itself (forming a linked-list of the stack frames) or in registers (e.g. the x29 register on AArch64, or RBP register on x86-64).</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="806" height="541" src="https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram.webp" alt="Explanatory diagram of frame pointers, showing a link from the x86-64 register %rbp to the start of the current frame, which holds the prior value of %rbp that points to the top of the previous frame, and so on." class="wp-image-7549" srcset="https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram.webp 806w, https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram-256x172.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram-512x344.webp 512w, https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram@2x.webp 1612w" sizes="auto, (max-width: 806px) 100vw, 806px" /><figcaption class="wp-element-caption">Diagram <a href="https://fedoraproject.org/wiki/Changes/fno-omit-frame-pointer" data-wpel-link="external" target="_blank" rel="external noopener">courtesy of the Fedora Project</a> (specific author unknown).</figcaption></figure>
</div>


<p>The controversial part &#8211; insofar as there is any controversy &#8211; is in dedicating a CPU register to hold a frame pointer (to point to the start of the current stack frame).  It&#8217;s super convenient for a lot of things, but particularly for debuggers and profilers as it gives them a reliable and very fast way to find the top of the current callstack.  But it&#8217;s not <em>technically</em> required for the program to function.</p>



<p>No live CPU architectures, that I&#8217;m aware of, have a dedicated hardware register for frame pointers.  So you nominally have to &#8220;give up&#8221; a GPR (general-purpose register) in order to have a frame pointer.</p>
</div></div>



<p><a href="https://github.com/FranzBusch" data-wpel-link="external" target="_blank" rel="external noopener">Franz Busch</a> <a href="https://forums.swift.org/t/se-0419-swift-backtracing-api/69595/12" data-wpel-link="external" target="_blank" rel="external noopener">pointed out</a> that some notable software <em>still</em> ships with frame pointers omitted, e.g. apparently some major Linux distros.  I suspect it&#8217;s merely some inertia (or simply oversight) that&#8217;s delaying getting people off of that old crutch.  I&#8217;m not remotely surprised that some big Linux distros are in this bucket &#8211; they tend to be absurdly conservative and slow to change<sup data-fn="6e48e51a-6c80-46a9-b2d0-4729eb123f42" class="fn"><a href="#6e48e51a-6c80-46a9-b2d0-4729eb123f42" id="6e48e51a-6c80-46a9-b2d0-4729eb123f42-link">1</a></sup>.  And it&#8217;s mind-boggling how much vitriol restoring frame pointers generates from <a href="https://news.ycombinator.com/item?id=34632677" data-wpel-link="external" target="_blank" rel="external noopener">the peanut gallery</a>.</p>



<p>From watches to servers these days &#8211; and frankly most of the embedded space, since it&#8217;s mostly <a href="https://en.wikipedia.org/wiki/ARM_architecture_family#32-bit_architecture" data-wpel-link="external" target="_blank" rel="external noopener">ARM</a> &#8211; everything generally has an ISA with sufficiently many GPRs to negate any big benefit from omitting frame pointers.  Giving up one of 31 GPRs (for e.g. <a href="https://en.wikipedia.org/wiki/AArch64" data-wpel-link="external" target="_blank" rel="external noopener">AArch64</a>, the dominant CPU architecture family today) is pretty insignificant for the vast majority of code, because almost nothing actually uses all 31 GPRs anyway.  It only makes a significant difference<sup data-fn="188c96e5-8b0d-4e30-84f9-989822dfd065" class="fn"><a href="#188c96e5-8b0d-4e30-84f9-989822dfd065" id="188c96e5-8b0d-4e30-84f9-989822dfd065-link">2</a></sup> when the CPU design is register-starved to begin with, like <a href="https://en.wikipedia.org/wiki/IA-32" data-wpel-link="external" target="_blank" rel="external noopener">i386</a>.  And those architectures are largely dead, in museums, or restricted to <em>very</em> tiny CPUs as used in some microcontrollers (&#8220;embedded&#8221; systems).</p>



<p>Even back when i386 et al were still a concern, the proponents of <code>-fomit-frame-pointer</code> often argued not on the potential merits of the trade-off, but rather that it was a &#8220;free&#8221; performance boost, so even if it was only by a percentage point or two, why not?  They of course were either naively or deliberately overlooking the detrimental effects.</p>



<p>There may still be software for which omitting frame pointers is the right trade-off, even on modern CPUs.  But I find it hard to believe there&#8217;s <em>enough</em> cases like that to warrant accomodation in standard tools.</p>



<h3 class="wp-block-heading">A brief trip back to Apple circa 2007</h3>



<p>Back in the brief window of time when i386 was a thing for the Mac (32-bit Intel, e.g. <a href="https://en.wikipedia.org/wiki/Intel_Core#Core" data-wpel-link="external" target="_blank" rel="external noopener">Core Duos</a><sup data-fn="b67d5fbb-7aa1-4c29-bc93-1341ac28771a" class="fn"><a href="#b67d5fbb-7aa1-4c29-bc93-1341ac28771a" id="b67d5fbb-7aa1-4c29-bc93-1341ac28771a-link">3</a></sup> as used in <a href="https://everymac.com/systems/apple/macbook/specs/macbook_1.83.html" data-wpel-link="external" target="_blank" rel="external noopener">the first MacBooks</a>), I was at Apple in the Performance Tools teams (<a href="https://web.archive.org/web/20100124025810/https://developer.apple.com/tools/sharkoptimize.html" data-wpel-link="external" target="_blank" rel="external noopener">Shark</a> &amp; <a href="https://help.apple.com/instruments/mac/current/#/dev7b09c84f5" data-wpel-link="external" target="_blank" rel="external noopener">Instruments</a>), and it was a frustration of ours that&nbsp;<code>-fomit-frame-pointer</code>&nbsp;<em>was</em>&nbsp;a noticeable performance-booster on the register-starved i386<sup data-fn="29afcd2a-0449-44c0-a274-0b06c9ddce8a" class="fn"><a href="#29afcd2a-0449-44c0-a274-0b06c9ddce8a" id="29afcd2a-0449-44c0-a274-0b06c9ddce8a-link">4</a></sup> architecture<sup data-fn="e0287bfa-c0ab-44b3-8fef-812983216ca6" class="fn"><a href="#e0287bfa-c0ab-44b3-8fef-812983216ca6" id="e0287bfa-c0ab-44b3-8fef-812983216ca6-link">5</a></sup>, so it was hard to just bluntly tell people not to use it… yet, by breaking the ability to profile their code, people who used it often left even&nbsp;<em>bigger</em>&nbsp;performance gains on the table (or otherwise had to invest much more labour into identifying &amp; resolving performance problems).</p>



<p>At one point there was even an Apple-internal debate about whether to abandon kernel-based profiling in favour of user-space profiling<sup data-fn="d563aa47-98d0-4761-9c0f-63194d9f7d20" class="fn"><a href="#d563aa47-98d0-4761-9c0f-63194d9f7d20" id="d563aa47-98d0-4761-9c0f-63194d9f7d20-link">6</a></sup> because <a href="https://developers.redhat.com/articles/2023/07/31/frame-pointers-untangling-unwinding#where_do_frame_pointers_fit_into_this_" data-wpel-link="external" target="_blank" rel="external noopener">implementing backtracing without frame pointers is&nbsp;<em>possible</em></a>&nbsp;but <a href="https://rwmj.wordpress.com/2023/02/14/frame-pointers-vs-dwarf-my-verdict/" data-wpel-link="external" target="_blank" rel="external noopener">very expensive</a> and requires masses of debug metadata (e.g. <a href="https://en.wikipedia.org/wiki/DWARF" data-wpel-link="external" target="_blank" rel="external noopener">DWARF</a>), making it highly unpalatable to put in the kernel. Thankfully there were too many obvious problems with user-space profiling, so that notion never really got its legs, and then x86-64 finally arrived<sup data-fn="3ad966e2-a3a1-41ee-a13a-84e58f5e8981" class="fn"><a href="#3ad966e2-a3a1-41ee-a13a-84e58f5e8981" id="3ad966e2-a3a1-41ee-a13a-84e58f5e8981-link">7</a></sup> and it was mooted.</p>


<ol class="wp-block-footnotes"><li id="6e48e51a-6c80-46a9-b2d0-4729eb123f42">e.g. <a href="https://wadetregaskis.com/how-to-install-imagemagick-7-for-wordpress-under-plesk-obsidian-on-ubuntu-22-04/" data-wpel-link="internal">Ubuntu <em>still</em> not officially supporting ImageMagick 7</a> even though it&#8217;s been out for nearly a decade. <a href="#6e48e51a-6c80-46a9-b2d0-4729eb123f42-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="188c96e5-8b0d-4e30-84f9-989822dfd065">Aside from the question of register space, there <em>is</em> additional cost to implementing frame pointers, as additional instructions are required around function entry &amp; exit in order to maintain the frame pointers &#8211; to push &amp; pop them off the stack, etc.  The cost of those is usually insignificant &#8211; especially in <a href="https://en.wikipedia.org/wiki/Superscalar_processor" data-wpel-link="external" target="_blank" rel="external noopener">superscalar</a> microarchitectures, as is the norm &#8211; so that aspect is not typically the focus of the controversy. <a href="#188c96e5-8b0d-4e30-84f9-989822dfd065-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="b67d5fbb-7aa1-4c29-bc93-1341ac28771a">Tangentially, I vaguely recall us Apple engineers kinda hating the Core Duo (Yonah), or more specifically Apple&#8217;s choice to use it.  Apple used them only for a tiny window of time, from May 2006 to about November 2006 when the Core 2 Duo (Merom) finally replaced them across the line.  I don&#8217;t recall <em>all</em> the reasons that the Core 2 Duo was superior, but they included that Core 2 Duo corrected the 32-bit regression (for Macs) and performed <em>much</em> better.  Anytime Apple releases a Mac with a dud processor in it, like those Core Duos, a lot of Apple engineers die a little inside because they know they&#8217;re going to be stuck supporting the damn things for many years even after the last cursed one rolls off the assembly line.<br><br>It&#8217;s still a mystery to me why Apple rushed the Intel transition in this regard.  They only had to wait six more months and they could have had a clean start on Intel, with no 32-bit to burden on them for the next seven years. <a href="#b67d5fbb-7aa1-4c29-bc93-1341ac28771a-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="29afcd2a-0449-44c0-a274-0b06c9ddce8a">Why do I keep calling it &#8220;i386&#8221;?  Isn&#8217;t it officially &#8220;IA-32&#8221;?  Well, yes, but that&#8217;s (a) only retroactively and (b) only ever used by Intel.  Though I guess &#8220;x86&#8221; is probably the more common name?  Yet &#8220;i386&#8221; is in my mental muscle memory.  Maybe that&#8217;s just how we used to refer to it, at Apple?  Maybe just because that&#8217;s the name used in gcc / clang arch &amp; target flags?<br><br>Incidentally, <code>clang -arch i386 -print-supported-cpus</code> on my M2 MacBook Air still lists Yonah (those damn Core Duos) as supported.  Gah!  They won&#8217;t die! 😆 <a href="#29afcd2a-0449-44c0-a274-0b06c9ddce8a-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="e0287bfa-c0ab-44b3-8fef-812983216ca6">It&#8217;s funny how the Intel transition is now heralded as being amazing and how much better Intel Macs were than PPC Macs, but for a while there we lost a <em>lot</em> of things, like a 64-bit architecture, an excellent SIMD implementation, and the notion of more than [effectively] six GPRs. <img loading="lazy" decoding="async" width="20" height="20" src="https://emoji.discourse-cdn.com/apple/stuck_out_tongue_closed_eyes.png?v=12" alt=":stuck_out_tongue_closed_eyes:"> <a href="#e0287bfa-c0ab-44b3-8fef-812983216ca6-link" aria-label="Jump to footnote reference 5">↩︎</a></li><li id="d563aa47-98d0-4761-9c0f-63194d9f7d20">There were at the time already some Apple developer tools that did user-space profiling, most notably Sampler (now a niche feature in Activity Monitor) and early versions of Instruments (in fact Instruments <em>still</em> has the Sampler plug-in which does this, although I can&#8217;t really fathom why anyone would ever intentionally use it over the Time Profiler plug-in). <a href="#d563aa47-98d0-4761-9c0f-63194d9f7d20-link" aria-label="Jump to footnote reference 6">↩︎</a></li><li id="3ad966e2-a3a1-41ee-a13a-84e58f5e8981">In the sense of <em>all</em> Macs adopting it, not just the Mac Pro.  It was easy to ignore i386 at that point because it was then all but officially a dead architecture as far as Apple were concerned. <a href="#3ad966e2-a3a1-41ee-a13a-84e58f5e8981-link" aria-label="Jump to footnote reference 7">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fomit-frame-pointer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/Frame-pointers-explanatory-diagram.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7536</post-id>	</item>
		<item>
		<title>NSImage is dangerous</title>
		<link>https://wadetregaskis.com/nsimage-is-dangerous/</link>
					<comments>https://wadetregaskis.com/nsimage-is-dangerous/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 23 Jan 2024 21:53:57 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[NSBitmapImageRep]]></category>
		<category><![CDATA[NSImage]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[unsafe]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7501</guid>

					<description><![CDATA[NSImage is formally documented as largely not thread-safe: The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details. Apple&#8217;s Threading Programming Guide &#62; Appendix A:&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nsimage-is-dangerous/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> is <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">formally documented</a> as largely <em>not</em> thread-safe:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Threading Programming Guide</a> &gt; <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Appendix A: Thread Safety Summary</a>, subsection <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a></cite></blockquote>



<p>What &#8220;in most cases&#8221; means is left to the reader&#8217;s imagination.  Apple adds a little addendum for <code>NSImage</code> specifically:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>One thread can create an <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> object, draw to the image buffer, and pass it off to the main thread for drawing. The underlying image cache is shared among all threads. For more information about images and how caching works, see <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40003290" data-wpel-link="external" target="_blank" rel="external noopener">Cocoa Drawing Guide</a>. </p>
<cite><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728" data-wpel-link="external" target="_blank" rel="external noopener">NSImage Restrictions</a></cite></blockquote>



<p>For a start, it&#8217;s talking only about <em>creating an NSImage from scratch</em>, not loading it from serialised form (e.g. a file, a pasteboard, etc).  It doesn&#8217;t even deign to mention those other, much more common cases.</p>



<p>And even for that one mentioned use case, what does it mean, exactly?  What &#8220;image cache&#8221; is it referring to?</p>



<p>I don&#8217;t have authoritative answers.  The documentation is so infuriatingly vague that Apple could do basically anything to the implementation, between macOS updates, and claim to have broken no promises.</p>



<p>What I do have is some empirical data and the results of some reverse engineering (shout out to <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a>), from macOS 14.2 Sonoma.</p>



<h2 class="wp-block-heading">NSImage 101</h2>



<p><code>NSImage</code>s can represent a wide range of imagery.  Most uses of them are probably for bitmap data (i.e. what you find in common image formats like <a href="https://en.wikipedia.org/wiki/WebP" data-wpel-link="external" target="_blank" rel="external noopener">WebP</a> &amp; <a href="https://en.wikipedia.org/wiki/AVIF" data-wpel-link="external" target="_blank" rel="external noopener">AVIF</a>), but <code>NSImage</code> also supports &#8216;raw&#8217; images (e.g. <a href="https://www.nikonusa.com/learn-and-explore/c/products-and-innovation/nikon-electronic-format-nef" data-wpel-link="external" target="_blank" rel="external noopener">Nikon NEF</a>) as well as vector data (e.g. <a href="https://en.wikipedia.org/wiki/SVG" data-wpel-link="external" target="_blank" rel="external noopener">SVG</a> &amp; <a href="https://en.wikipedia.org/wiki/PDF" data-wpel-link="external" target="_blank" rel="external noopener">PDF</a>).  It also has a plug-in mechanism of sorts, so the supported image formats can be extended dynamically at runtime.</p>



<p>You can fetch the full list of supported types from <a href="https://developer.apple.com/documentation/appkit/nsimage/1519988-imagetypes" data-wpel-link="external" target="_blank" rel="external noopener">NSImage.imageTypes</a> &#8211; although it doesn&#8217;t distinguish between those it can read vs those it can write.  Fortunately, <code>sips --formats</code> in Terminal gives you the same list with additional metadata.  On my machine that list happens to be:</p>



<pre class="wp-block-preformatted">Supported Formats:
-------------------------------------------
com.adobe.pdf                pdf   Writable
com.adobe.photoshop-image    psd   Writable
com.adobe.raw-image          dng   
com.apple.atx                --    Writable
com.apple.icns               icns  Writable
com.apple.pict               pict  
com.canon.cr2-raw-image      cr2   
com.canon.cr3-raw-image      cr3   
com.canon.crw-raw-image      crw   
com.canon.tif-raw-image      tif   
com.compuserve.gif           gif   Writable
com.dxo.raw-image            dxo   
com.epson.raw-image          erf   
com.fuji.raw-image           raf   
com.hasselblad.3fr-raw-image 3fr   
com.hasselblad.fff-raw-image fff   
com.ilm.openexr-image        exr   Writable
com.kodak.raw-image          dcr   
com.konicaminolta.raw-image  mrw   
com.leafamerica.raw-image    mos   
com.leica.raw-image          raw   
com.leica.rwl-raw-image      rwl   
com.microsoft.bmp            bmp   Writable
com.microsoft.cur            --    
com.microsoft.dds            dds   Writable
com.microsoft.ico            ico   Writable
com.nikon.nrw-raw-image      nrw   
com.nikon.raw-image          nef   
com.olympus.or-raw-image     orf   
com.olympus.raw-image        orf   
com.olympus.sr-raw-image     orf   
com.panasonic.raw-image      raw   
com.panasonic.rw2-raw-image  rw2   
com.pentax.raw-image         pef   
com.phaseone.raw-image       iiq   
com.samsung.raw-image        srw   
com.sgi.sgi-image            sgi   
com.sony.arw-raw-image       arw   
com.sony.raw-image           srf   
com.sony.sr2-raw-image       sr2   
com.truevision.tga-image     tga   Writable
org.khronos.astc             astc  Writable
org.khronos.ktx              ktx   Writable
org.khronos.ktx2             --    Writable
org.webmproject.webp         webp  
public.avci                  avci  
public.avif                  avif  
public.avis                  --    
public.heic                  heic  Writable
public.heics                 heics Writable
public.heif                  heif  
public.jpeg                  jpeg  Writable
public.jpeg-2000             jp2   Writable
public.jpeg-xl               jxl   
public.mpo-image             mpo   
public.pbm                   pbm   Writable
public.png                   png   Writable
public.pvr                   pvr   Writable
public.radiance              pic   
public.svg-image             svg   
public.tiff                  tiff  Writable</pre>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Sidenote:  notice how it supports modern formats like WebP, AVIF, and JPEG-XL, but <em>only</em> for reading, not writing. 😕</p>



<p>It used to be that <code>NSImage</code> was pretty much a one-stop-shop for typical app image I/O needs, but Apple have for some reason crippled it over the years.  I feel like this is part of a larger trend of Apple providing increasingly less functionality, more complexity, and less coherence in their frameworks.</p>
</div></div>



<p>An <code>NSImage</code> can have multiple &#8216;<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW9" data-wpel-link="external" target="_blank" rel="external noopener">representations</a>&#8216;.  These are essentially just different versions of the image.  One representation might be the original vector form (e.g. an <code><a href="https://developer.apple.com/documentation/appkit/nspdfimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSPDFImageRep</a></code>).  Another might be a rasterisation of that at a certain resolution.  Yet another might be a rasterisation at a different resolution.</p>



<p>It gets more complicated, however, because some representations are merely proxies for <em>other</em> frameworks&#8217; representations, like <a href="https://developer.apple.com/documentation/coregraphics/cgimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CGImage</code></a>, <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CIImage</code></a> or somesuch.</p>



<p>Nonetheless, for the simple case of a static bitmap image read from a file, generally <code>NSImage</code> produces just one representation, which is the full bitmap (as an <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code>).  That&#8217;s the only case I&#8217;m going to discuss here, for simplicity&#8217;s sake (though likely the lessons apply to the other cases too).</p>



<h2 class="wp-block-heading">Accessing / using an NSImage</h2>



<p>Generally to use an <code>NSImage</code> you need a bitmap representation.  e.g. to actually draw it to the screen.</p>



<p>Unless you manually create an <code>NSImage</code> from an in-memory bitmap, the bitmap representation is not loaded initially, but rather only when first needed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You can usually obtain the <code>NSBitmapImageRep</code> (via e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519961-bestrepresentation" data-wpel-link="external" target="_blank" rel="external noopener">bestRepresentation(for: .infinity, context: nil, hints: nil)</a></code>) even before it&#8217;s actually been loaded &#8211; initially it&#8217;s largely just a shell, that knows its metadata (e.g. dimensions and colour space) but not yet its actual imagery.</p>
</div></div>



<p>Ultimately the load is triggered when something asks for the raw bytes of the bitmap (either you, directly in your code, or indirectly when you e.g. ask AppKit to draw the image).</p>



<p>Fetching the raw bytes of a bitmap from <code>NSBitmapImageRep</code> is not a trivial exercise.  I&#8217;m going to gloss over the complexities (like planar data formats) and just talk about the common case of single-plane (&#8220;interleaved channels&#8221;) bitmaps.</p>



<p>For those, you can access the raw bytes via the <a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395421-bitmapdata" data-wpel-link="external" target="_blank" rel="external noopener"><code>bitmapData</code></a> property.</p>



<p>Nominally, <code>bitmapData</code> just returns a <code>uint8_t*</code>.</p>



<p>In fact, <code>bitmapData</code> calls a <em>lot</em> of internal methods in a complicated fashion, with many possible code paths.  What exactly it does depends on the underlying source of data, but in a nutshell it checks if the desired data has already been created / loaded (it&#8217;s cached in an instance variable) and if not it loads it, e.g. by calling <code><a href="https://developer.apple.com/documentation/imageio/1465011-cgimagesourcecreateimageatindex" data-wpel-link="external" target="_blank" rel="external noopener">CGImageSourceCreateImageAtIndex</a></code>.</p>



<p>That&#8217;s what makes <code>NSImage</code> dangerous to use from multiple threads simultaneously.</p>



<figure class="wp-block-pullquote"><blockquote><p>There is no mutual exclusion protecting any of these code paths.</p></blockquote></figure>



<p>Reading the <em>cached</em> bitmap data is essentially read-only (just some retain/autorelease traffic) so it&#8217;s safe to call from multiple threads concurrently (as long as something ensures the <code>NSBitmapImageRep</code> is kept alive the whole time <em>and not mutated</em>)</p>



<p>But loading or modifying it is not.  As such, if you call <code>bitmapData</code> from multiple threads concurrently, and you don&#8217;t know for sure that it&#8217;s already fully loaded, you get a <a href="https://www.avanderlee.com/swift/race-condition-vs-data-race/" data-wpel-link="external" target="_blank" rel="external noopener">data race</a> (also known as a &#8220;WTF does my app crash randomly?!&#8221; condition).</p>



<p>The consequences of that race vary.  Maybe you &#8220;win&#8221; the race &#8211; one thread happens to run virtually to completion of <code>bitmapData</code> first, storing the fresh backing data into the caching instance member, and then all the other threads run and just return that same value &#8211; the ideal situation as everything works as intended.</p>



<p>Maybe you &#8220;lose&#8221; the race: every concurrent thread checks simultaneously and sees there&#8217;s no cached value, so they all &#8211; in parallel and redundant to each other &#8211; load the bitmap data and store it into the cache.  They each return the one they created, even though ultimately only one thread wins &#8211; the <em>last</em> one to write into the cache &#8211; and the duplicate bitmap data that all the earlier threads created is deallocated.  Even though pointers to them have been returned to you.  And you might be in the middle of using them.  Causing you to crash with a memory protection fault (or worse, read from some random other memory allocation that happened to be placed at the same address afterwards, reading essentially garbage).</p>



<p>Hypocritically, this is because Apple don&#8217;t follow <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">their own rules on escaping pointers</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The pointer you pass to the function is only guaranteed to be valid for the duration of the function call. Do not persist the pointer and access it after the function has returned.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swift/swift-standard-library" data-wpel-link="external" target="_blank" rel="external noopener">Swift Standard Library</a> &gt; <a href="https://developer.apple.com/documentation/swift/manual-memory-management" data-wpel-link="external" target="_blank" rel="external noopener">Manual Memory Management</a> &gt; <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters" data-wpel-link="external" target="_blank" rel="external noopener">Calling Functions With Pointer Parameters</a>, subsection <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">Pass a Constant Pointer as a Parameter</a>.</cite></blockquote>



<p>The source code for the relevant <code>NSBitmapImageRep</code> methods is essentially:</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:4;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: #000000">- (</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">*)bitmapData {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">uint8_t</span><span style="color: #000000"> **result = </span><span style="color: #0000FF">nil</span><span style="color: #000000">;</span></span>
<span class="line"><span style="color: #000000">    [</span><span style="color: #0000FF">self</span><span style="color: #000000"> </span><span style="color: #795E26">getBitmapDataPlanes:</span><span style="color: #000000">&amp;result];</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> *result;</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">- (</span><span style="color: #0000FF">void</span><span style="color: #000000">)getBitmapDataPlanes:(</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">***)output {</span></span>
<span class="line"><span style="color: #000000">    [</span><span style="color: #0000FF">self</span><span style="color: #000000"> </span><span style="color: #795E26">_performBlockUsingBackingMutableData:</span><span style="color: #000000">^</span><span style="color: #795E26">void</span><span style="color: #000000"> (</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">* </span><span style="color: #001080">dataPlanes</span><span style="color: #000000">[</span><span style="color: #098658">5</span><span style="color: #000000">]) {</span></span>
<span class="line"><span style="color: #000000">        *output = dataPlanes;</span></span>
<span class="line"><span style="color: #000000">    }];</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="has-text-align-center has-x-large-font-size">🤦‍♂️</p>



<figure class="wp-block-pullquote"><blockquote><p>The <code>bitmapData</code> property and <code>getBitmapDataPlanes</code> methods are fundamentally unsafe.</p></blockquote></figure>



<p>Unfortunately, while <code>_performBlockUsingBackingMutableData</code> and its many similar siblings <em>can</em> be made safe, they are (a) all private and (b) not currently enforcing the necessary mutual exclusion.  This could be corrected by Apple in future (although I wouldn&#8217;t hold your breath).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Nominally the <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395583-colorat" data-wpel-link="external" target="_blank" rel="external noopener">colorAt(x:y:)</a></code> method could also be safe, but it currently lacks the same underlying mutual exclusion &#8211; and even if it didn&#8217;t, the performance when using it is atrocious due to the Objective-C method call overhead.  Even utilising <a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeSpeed/Articles/CriticalCode.html#//apple_ref/doc/uid/20001871-98344" data-wpel-link="external" target="_blank" rel="external noopener">IMP caching</a>, the performance is still terrible compared to directly accessing the bitmap byte buffer.</p>
</div></div>



<h2 class="wp-block-heading">Why does it matter that NSImage is not thread-safe?</h2>



<p>The crux of the problem is that you often have no good choice about it, because <code>NSImage</code> is a <a href="https://forums.swift.org/t/what-does-currency-type-mean/41065" data-wpel-link="external" target="_blank" rel="external noopener">currency type</a> used widely throughout Apple&#8217;s own frameworks, and you often have no control over what thread it&#8217;s created or used on.</p>



<p>For example, even using the very latest Apple APIs such as SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/dropdestination(for:action:istargeted:)" data-wpel-link="external" target="_blank" rel="external noopener">dropDestination(for:action:isTargeted:)</a></code>, you cannot control which thread the <code>NSImage</code>s are created on (it <em>appears</em> to be the main thread, although that API provides no guarantees).</p>



<p>Similarly you have no control over where those images are used if you pass them to 3rd party code &#8211; including Apple&#8217;s.  e.g. <code><a href="https://developer.apple.com/documentation/swiftui/image/init(nsimage:)" data-wpel-link="external" target="_blank" rel="external noopener">Image(nsImage:)</a></code>; <em>possibly</em> that only uses them on the main thread, but it <em>might</em> be pre-rendered the image a separate thread for better performance (to avoid blocking the main thread and causing the app to hang).  In fact it <em>should</em>, in principle.</p>



<p>Loading an image &#8211; actually reading it from a file or URL and decompressing it into a raw bitmap suitable for drawing to the screen &#8211; should never be done on the main thread, because it can take a long time.  A 1 GiB TIFF takes nearly 30 seconds on my iMac Pro, for example (and TIFF uses very lightweight compression, so it&#8217;s a relatively fast-to-read format).  Anything involving the network could take an unbounded amount of time.  Even small files &#8211; like a 20 MiB NEF &#8211; can take seconds to render because they are non-trivial to decode and/or decompress.</p>



<p>So you&#8217;re screwed on multiple levels:  not only can you generally not guarantee what thread <code>NSImage</code> is born on nor used on, you <em>can&#8217;t</em> use it exclusively on the main thread because that will cause a terrible user experience.</p>



<h2 class="wp-block-heading">What can you do?</h2>



<p>In simple terms, the best you can realistically do is try ensure that all modifications to the <code>NSImage</code> (including implicit ones, such as loading a bitmap representation upon first use) happen exclusively in one thread [at a time].  For example, in my experiments, putting a lock around otherwise concurrent calls to <code>bitmapData</code> is enough to prevent any data races.  Although I am mystified as to how I can still let the main thread draw the image concurrently without any apparent problems. 🤔</p>



<p>If you want to play it <em>really</em> safe, you have to create &amp; pre-load each <code>NSImage</code> on a single thread (<em>not</em> the main thread), before ever sending it to another isolation domain.  That means manually reimplementing things like drag and drop of images (because you can no longer work directly with <code>NSImage</code> with any drag &amp; drop APIs, you have to instead use only &#8211; and <em>all</em> &#8211; the things that <em>could potentially be</em> images, like URLs or data blobs, and then translate those to &amp; from <code>NSImage</code>s manually).</p>



<p>Complicating all this is that <code>NSImage</code> is unclear about how <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW11" data-wpel-link="external" target="_blank" rel="external noopener">its caching</a> works.  For starters, does this caching apply to the representations or is it a hidden, orthogonal system?  Is it thread-safe?  Etc.</p>



<p>You can supposedly modify the caching behaviour via the <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519850-cachemode" data-wpel-link="external" target="_blank" rel="external noopener">cacheMode</a></code> property, but in my experience there is no apparent effect no matter what it is set it to (not on the image&#8217;s representations nor on render performance in the GUI).</p>



<p>It&#8217;s a shame that <code>NSImage</code> has been so neglected, and has so many glaring problems.  Over the years Apple have seemingly tried to replace it, introducing new image types <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> and <a href="https://developer.apple.com/documentation/uikit/uiimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> again, but all that&#8217;s done is <a href="https://xkcd.com/927/" data-wpel-link="external" target="_blank" rel="external noopener">made everything more complicated</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nsimage-is-dangerous/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/NSImage-thread-unsafety-caught-by-Address-Sanitizer-MallocScribble.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7501</post-id>	</item>
		<item>
		<title>Reminder: macOS system frameworks binaries are hidden (since Big Sur)</title>
		<link>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/</link>
					<comments>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 23 Jan 2024 18:59:12 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[dyld]]></category>
		<category><![CDATA[dyld-shared-cache-extractor]]></category>
		<category><![CDATA[Hopper]]></category>
		<category><![CDATA[macOS]]></category>
		<category><![CDATA[macOS Big Sur]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7503</guid>

					<description><![CDATA[Every now and again I&#8217;ll go to do something really innocuous with an Apple framework, like disassemble it in Hopper or check the link headers. And every. single. time. I forget that Apple did some really weird shit in Big Sur, and removed the binaries. $ ls -lh /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit ls: /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit: No such file or&#8230; <a class="read-more-link" href="https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Every now and again I&#8217;ll go to do something really innocuous with an Apple framework, like disassemble it in <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a> or check the link headers.  And <em>every. single. time</em>. I forget that Apple did some really weird shit in Big Sur, and removed the binaries.</p>



<pre class="wp-block-preformatted">$ ls -lh /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit
ls: /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit: No such file or directory</pre>



<p>WTF, mate?</p>



<p>Invariably I spend half an hour websearching around to try to figure out how the hell my system got so broken, and how it&#8217;s possible to even boot macOS in such a corrupt state, until <em>finally</em> I chance upon <a href="https://mjtsai.com/blog/2020/06/26/reverse-engineering-macos-11-0/" data-wpel-link="external" target="_blank" rel="external noopener">Michael Tsai&#8217;s excellent summary of how Apple broke their frameworks starting in Big Sur</a>.</p>



<p>The good news for Hopper is that it has since been updated to work around this &#8211; you can access the Apple framework binaries through <em>File</em> > <em>Read File from DYLD Cache…</em>  There&#8217;s also tools like <a href="https://github.com/keith/dyld-shared-cache-extractor" data-wpel-link="external" target="_blank" rel="external noopener">dyld-shared-cache-extractor</a> which can resurrect the binaries from the cache.</p>



<p>Note also that in Sonoma, at least, the cache lives at <code>/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/</code> (in previous macOS releases it was apparently in <code>/System/Library/dyld/</code>).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7503</post-id>	</item>
		<item>
		<title>I was into io_uring before it existed</title>
		<link>https://wadetregaskis.com/i-was-into-io_uring-before-it-existed/</link>
					<comments>https://wadetregaskis.com/i-was-into-io_uring-before-it-existed/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 17 Jan 2024 08:07:54 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Instruments]]></category>
		<category><![CDATA[io_uring]]></category>
		<category><![CDATA[rample]]></category>
		<category><![CDATA[Shark]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7419</guid>

					<description><![CDATA[I just read up a bit on io_uring, prompted by a Swift Forums thread relating to it, and it made me laugh. To a lot of people it&#8217;s an amazing new[ish] high-performance I/O system for Linux. Which it is (albeit with some serious security concerns, apparently). A lot of people are very excited by it.&#8230; <a class="read-more-link" href="https://wadetregaskis.com/i-was-into-io_uring-before-it-existed/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I just read up a bit on <a href="https://man.archlinux.org/man/io_uring.7" data-wpel-link="external" target="_blank" rel="external noopener">io_uring</a>, prompted by <a href="https://forums.swift.org/t/blocking-i-o-and-concurrency/67276" data-wpel-link="external" target="_blank" rel="external noopener">a Swift Forums thread relating to it</a>, and it made me laugh.  To a lot of people it&#8217;s an amazing new[ish] high-performance I/O system for Linux.  Which it is (albeit <a href="https://security.googleblog.com/2023/06/learnings-from-kctf-vrps-42-linux.html" data-wpel-link="external" target="_blank" rel="external noopener">with some serious security concerns</a>, apparently).  A lot of people are very excited by it.  Which they should be.  But it&#8217;s not new or novel, despite what many folks seem to think.</p>



<p>It immediately reminded me of the Shark 5 re-implementation of the profiling interface between the kernel and Shark.  Shark 5 of course didn&#8217;t actually survive to birth &#8211; it was snuffed out by politics and, admittedly, a bit of our own hubris in the Shark team &#8211; but that underlying infrastructure did, as the guts of <a href="https://help.apple.com/instruments/mac/current/#" data-wpel-link="external" target="_blank" rel="external noopener">Instruments</a>&#8216; Time Profiler and System Trace features.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>I&#8217;m not even <em>pretending</em> to claim that what we came up with for Shark 5 was novel, either.  As far as we know it was novel to our domain, of profiling tools, but I&#8217;d be amazed if there aren&#8217;t earlier implementations of the same sort of thing many decades prior.</p>
</div></div>



<p>In Shark 4 and earlier, userspace would allocate a single big buffer of memory for the profiling data, hand it [back] to the kernel<sup data-fn="e9992b27-788f-4f5c-9c79-5c6c2396f729" class="fn"><a href="#e9992b27-788f-4f5c-9c79-5c6c2396f729" id="e9992b27-788f-4f5c-9c79-5c6c2396f729-link">1</a></sup>, the kernel &#8211; specifically the Shark kernel extension &#8211; would write into that buffer, then when the buffer was full profiling would end<sup data-fn="eb054551-a35d-4599-9763-bc0d7415909a" class="fn"><a href="#eb054551-a35d-4599-9763-bc0d7415909a" id="eb054551-a35d-4599-9763-bc0d7415909a-link">2</a></sup>.  The userspace driver &#8211; Shark &#8211; <em>could</em> technically start profiling again immediately, but of course you&#8217;d often have a gap in your profiling &#8211; potentially a big one, for the more expensive profiling modes (e.g. System Trace) or on heavily-loaded machines.  Shark didn&#8217;t run with elevated priority, as far as I recall, so it could get crowded off the CPU(s) by other programs.  All communication between user &amp; kernel spaces was via Mach messages, which are ultimately syscalls, and was time-sensitive since kernel- &amp; user-space had to be in lock-step all the time.</p>



<p>For Shark 5, we wanted to up the ante and remove limitations.  We wanted to be able to record indefinitely, limited only by disk space.  More importantly, we also wanted to be able show profiling results <em>live</em>.</p>



<p>So, we &#8211; and I use the term loosely, as I think it was mostly <a href="https://www.linkedin.com/in/mxshift/" data-wpel-link="external" target="_blank" rel="external noopener">Rick Altherr</a> with possibly the help of <a href="https://www.linkedin.com/in/ryandubois/" data-wpel-link="external" target="_blank" rel="external noopener">Ryan du Bois</a> &#8211; came up with a mechanism that&#8217;s very similar to io_uring, but years earlier, in ~2008.  Userspace would allocate multiple smaller buffers &#8211; also easier to acquire given the requirement to be physically contiguous &#8211; and hand those to the kernel somewhat as needed.  Userspace merely needed to stay ahead of the kernel&#8217;s use, which didn&#8217;t necessarily mean pre-allocating all the buffers.  When each of those individual buffers filled up with profiling data from the kernel, they&#8217;d be made available back to the userspace side<sup data-fn="9a13c4b7-e511-4844-815e-13771d10af96" class="fn"><a href="#9a13c4b7-e511-4844-815e-13771d10af96" id="9a13c4b7-e511-4844-815e-13771d10af96-link">3</a></sup>.  Shark (in userspace) would optionally &#8211; if in &#8216;live&#8217; mode or if running short on empty buffers &#8211; process those buffers while profiling was still actively occurring, freeing them up to go back into the ring for the kernel&#8217;s use.  It did mean you slightly increased the probability of perturbing the program(s) under profiling, but that was mostly just a matter of ensuring the buffers were large enough to sufficiently amortise the cost of their processing &amp; handling.  I don&#8217;t recall the extent of the processing, but I think it was not necessarily much more than copying the data out into non-wired memory (maybe even a memory-mapped file?).  That was cheaper than allocating <em>and wiring</em> new buffers.</p>



<p>It worked really well, and is possibly still the implementation used to date &#8211; while it appears a lot of the frameworks (e.g. CoreProfile) have disappeared from macOS, presumably rewritten somewhere else, I&#8217;d be surprised if the kernel-user interface itself has changed much.  It was pretty much perfect.</p>



<p>The creation of this new, superior profiling system also spurred me to create <code>rample</code>, a CLI tool kind of like <code>top</code> which showed you what your CPUs were doing in real time<sup data-fn="4abb5c72-5961-4445-bddf-3ef39ffb6a43" class="fn"><a href="#4abb5c72-5961-4445-bddf-3ef39ffb6a43" id="4abb5c72-5961-4445-bddf-3ef39ffb6a43-link">4</a></sup> as a heavy tree, much like you&#8217;d get in a Time Profile in Shark (and later Instruments).  It was super quick to write &#8211; just a day or two, I believe &#8211; and yet I unexpectedly found that it was more useful than Shark itself.  Being able to see &#8211; practically instantly by virtue of how lightweight it is to launch a simple CLI program &#8211; what&#8217;s chewing on the CPU, <em>including inside the kernel</em>, was incredibly useful.  Instruments is the closest you can get today on a Mac, but it&#8217;s super slow and clunky in comparison.  It&#8217;s one of my top regrets, of my time at Apple, that I didn&#8217;t get <code>rample</code> into Mac OS X, or at least into the Dev Tools.</p>



<p></p>


<ol class="wp-block-footnotes"><li id="e9992b27-788f-4f5c-9c79-5c6c2396f729">I don&#8217;t recall precisely why, but it was apparently better (or only possible?) to allocate the buffer in userspace, even though that was ultimately done with syscalls serviced by the kernel.  I think it had something to do with being easier to handle failures, like being out of memory, in particular <em>contiguous</em> memory &#8211; the memory was wired during use to ensure the kernel wouldn&#8217;t have to deal with the practical nor performance problems of page faults while recording profiling data. <a href="#e9992b27-788f-4f5c-9c79-5c6c2396f729-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="eb054551-a35d-4599-9763-bc0d7415909a">Which was a problem because generally profiling sessions were specified to run for a specific time duration, by the end-user.  So the buffer had to be sized to avoid filling prematurely, which meant wasting memory. <a href="#eb054551-a35d-4599-9763-bc0d7415909a-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="9a13c4b7-e511-4844-815e-13771d10af96">Technically they were always available &#8211; we didn&#8217;t bother changing access permissions &#8211; but the userspace app would coordinate with the kernel via an elegant lockfree state machine.  That state machine was a superb design &#8211; it deserves its own detailed nostalgia trip. <a href="#9a13c4b7-e511-4844-815e-13771d10af96-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="4abb5c72-5961-4445-bddf-3ef39ffb6a43">Well, updated every second or so, like <code>top</code>. <a href="#4abb5c72-5961-4445-bddf-3ef39ffb6a43-link" aria-label="Jump to footnote reference 4">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/i-was-into-io_uring-before-it-existed/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7419</post-id>	</item>
		<item>
		<title>How well do you know your macOS keyboard shortcuts?</title>
		<link>https://wadetregaskis.com/how-well-do-you-know-your-macos-keyboard-shortcuts/</link>
					<comments>https://wadetregaskis.com/how-well-do-you-know-your-macos-keyboard-shortcuts/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 05 Jan 2024 03:31:17 +0000</pubDate>
				<category><![CDATA[Quizzes]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[keyboard shortcuts]]></category>
		<category><![CDATA[Mac OS]]></category>
		<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[macOS]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7356</guid>

					<description><![CDATA[A little quiz to separate the Mac power users from the padawans. <a class="read-more-link" href="https://wadetregaskis.com/how-well-do-you-know-your-macos-keyboard-shortcuts/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[



            <div class='ays-quiz-container ays_quiz_classic_light   ays-quiz-keyboard-active' data-quest-effect='fade'  data-hide-bg-image='false' id='ays-quiz-container-1'>
                
                
                <div class='ays-questions-container'>
                    <div class="ays-quiz-full-screen-wrap">
                <a class="ays-quiz-full-screen-container">
                    <svg xmlns="http://www.w3.org/2000/svg" height="24" fill="#fff" viewBox="0 0 24 24" width="24" class="ays-quiz-close-full-screen">
                        <path d="M0 0h24v24H0z" fill="none"/>
                        <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
                    </svg>
                    <svg xmlns="http://www.w3.org/2000/svg" height="24" fill="#fff" viewBox="0 0 24 24" width="24" class="ays-quiz-open-full-screen">
                        <path d="M0 0h24v24H0z" fill="none"/>
                        <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
                    </svg>
                </a>
            </div>
                    
                    
                    <form action='' method='post' id='ays_finish_quiz_1' 
                        class='ays-quiz-form enable_correction enable_questions_result '
                    >
            <input type='hidden' value='grid' class='answer_view_class'>
            <input type='hidden' value='' class='ays_qm_enable_arrows'>
            <audio id='ays_quiz_right_ans_sound_1' class='ays_quiz_right_ans_sound' src='https://wadetregaskis.com/wp-content/uploads/2024/01/Silence.m4a'></audio><audio id='ays_quiz_wrong_ans_sound_1' class='ays_quiz_wrong_ans_sound' src='https://wadetregaskis.com/wp-content/uploads/2024/01/Quack.m4a'></audio>
            
            <div class='step active-step'>
                <div class='ays-abs-fs ays-start-page'>
                    
                    
                    <p class='ays-fs-title'>macOS keyboard shortcuts quiz</p>
                    <div class='ays-fs-subtitle'><p><span style="font-size: 72px">⌃⌥⇧⌘🤔</span></p>
</div>
                    <input type='hidden' name='ays_quiz_id' value='1'/>
                    <input type='hidden' name='ays_quiz_finish_nonce' value='96143c3723'>
                    <input type='hidden' name='ays_quiz_curent_page_link' class='ays-quiz-curent-page-link' value='https://wadetregaskis.com/tags/apple/feed/'/>
                    <input type='hidden' name='ays_quiz_questions' value='4,7,10,9,11,3,6,13,31,26,28,15,8,12,5,16,27,25,21,30,2,1,14,22,29,19,18,24,20,17,23,32'>
                    
                    
                    
                <div class="ays-quiz-start-button-preloader">
                    <input type='button'  class='ays_next start_button action-button ays_quiz_enable_loader ays-quiz-keyboard-active' disabled='disabled' value='Loading ...' data-enable-leave-page="false" />
                    <img decoding="async" src="https://wadetregaskis.com/wp-content/plugins/quiz-maker/admin/images/loaders/tail-spin.svg" class="ays_quiz_start_button_loader">
                </div>
                    
                    </div>
                </div><div class='step ' data-question-id='4' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>1 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>Some important notes before we begin:</p>
<p>• This quiz assumes default system settings on macOS 14 Sonoma with a generic Apple English keyboard and locale.</p>
<p>• This quiz considers only <em>native</em> Mac behaviour.</p>
<p>• You can click &#8216;Next&#8217; without selecting an answer, but that <em>skips the question</em> and counts as a wrong answer in your final score.</p>
<p>• You cannot change your choice after clicking / tapping an answer.</p>
<p>&nbsp;</p>
<p>Now, before we begin for real, let&#8217;s make sure you understand how quizzes work.  Do you understand how quizzes work?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-4]' id='ays-answer-15-1' value='15'/>

                <label for='ays-answer-15-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Yes</label><label for='ays-answer-15-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-4]' id='ays-answer-16-1' value='16'/>

                <label for='ays-answer-16-1' class='  ays_position_initial  ays-quiz-keyboard-label'>No</label><label for='ays-answer-16-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['4'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ' style='display:none'>
                            <p>This is going to be fun for you, then. 😝</p>

                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='7' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>2 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this symbol mean:</p>
<p><span style="font-size: 72px">⌘</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-27-1' value='27'/>

                <label for='ays-answer-27-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Command</label><label for='ays-answer-27-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-31-1' value='31'/>

                <label for='ays-answer-31-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Apple</label><label for='ays-answer-31-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-30-1' value='30'/>

                <label for='ays-answer-30-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Function</label><label for='ays-answer-30-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-32-1' value='32'/>

                <label for='ays-answer-32-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Option</label><label for='ays-answer-32-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-29-1' value='29'/>

                <label for='ays-answer-29-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Place of interest</label><label for='ays-answer-29-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-7]' id='ays-answer-28-1' value='28'/>

                <label for='ays-answer-28-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Control</label><label for='ays-answer-28-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['7'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <h3>A little bit of history</h3>
<p>The Command key symbol, also known as the &#8220;looped square&#8221;, &#8220;Saint John&#8217;s Arms&#8221;, &#8220;Bowen knot&#8221;, &#8220;clover&#8221;, or &#8220;propeller&#8221; symbol, first appeared on <a href="https://www.flickr.com/photos/heritagefutures/3211074870" data-wpel-link="external" target="_blank" rel="external noopener">the keyboard</a> &amp; in the menus of the original Macintosh (1984).</p>
<p><a href="https://wadetregaskis.com/wp-content/uploads/2024/01/System-1-0-File-menu.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-7364" src="https://wadetregaskis.com/wp-content/uploads/2024/01/System-1-0-File-menu.webp" alt="Screenshot from Apple System 1.0 (the OS from the original Macintosh in 1984) showing the contents of the Finder File menu." width="512" height="342" srcset="https://wadetregaskis.com/wp-content/uploads/2024/01/System-1-0-File-menu-256x171@2x.webp 512w, https://wadetregaskis.com/wp-content/uploads/2024/01/System-1-0-File-menu-256x171.webp 256w" sizes="auto, (max-width: 512px) 100vw, 512px" /></a></p>
<p>It replaced the use of the Apple logo () on the Apple keys that were present on the keyboards for the <a href="https://www.flickr.com/photos/37796451@N00/4871313185" data-wpel-link="external" target="_blank" rel="external noopener">Apple IIe</a>, <a href="https://www.flickr.com/photos/teflon/2865505108" data-wpel-link="external" target="_blank" rel="external noopener">Apple III</a>, and the <a href="https://www.flickr.com/photos/75693106@N05/6946153772" data-wpel-link="external" target="_blank" rel="external noopener">Lisa</a>.  Unlike its predecessors, the Macintosh would show the keyboard shortcuts for menu commands in the menus themselves, using symbols for modifier keys like Command.  Since the symbol would be displayed <em>many</em> times all over the menus, <a href="https://www.folklore.org/Swedish_Campground.html" data-wpel-link="external" target="_blank" rel="external noopener">Steve Jobs felt use of the Apple logo would be dilute its value</a>.  He tasked <a href="https://kareprints.com/pages/about" data-wpel-link="external" target="_blank" rel="external noopener">Susan Kare</a>, principal &amp; beloved visual designer for the original Macintosh, to come up with a new symbol.  <a href="https://web.archive.org/web/20150527143227/https://web.stanford.edu/dept/SUL/library/mac/primary/interviews/kare/mac.html" data-wpel-link="external" target="_blank" rel="external noopener">She found the iconic symbol after pouring through book collections of symbols, looking for something &#8216;abstract&#8217;</a>.  It had previously been used very little, mainly just as a &#8216;place of interest&#8217; indicator on maps in some Nordic countries.</p>
<p><a href="https://www.unicodepedia.com/unicode/miscellaneous-technical/2318/place-of-interest-sign/" data-wpel-link="external" target="_blank" rel="external noopener">In Unicode it is formally still the &#8220;Place of interest sign&#8221;</a>, even though most of the world now knows it as the Command key symbol.</p>
<p>The Apple logo persisted alongside ⌘ on the key <a href="https://www.macworld.com/article/224807/think-retro-open-apple-closed-apple.html" data-wpel-link="external" target="_blank" rel="external noopener">until 2007</a>!</p>
<p>See its origin for yourself in <a href="https://infinitemac.org/1984/System%201.0" data-wpel-link="external" target="_blank" rel="external noopener">Macintosh System 1.0</a>!</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='10' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>3 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this symbol mean:</p>
<p><span style="font-size: 72px">⌥</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-45-1' value='45'/>

                <label for='ays-answer-45-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Place of interest</label><label for='ays-answer-45-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-48-1' value='48'/>

                <label for='ays-answer-48-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Option</label><label for='ays-answer-48-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-47-1' value='47'/>

                <label for='ays-answer-47-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Apple</label><label for='ays-answer-47-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-43-1' value='43'/>

                <label for='ays-answer-43-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Command</label><label for='ays-answer-43-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-44-1' value='44'/>

                <label for='ays-answer-44-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Control</label><label for='ays-answer-44-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-10]' id='ays-answer-46-1' value='46'/>

                <label for='ays-answer-46-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Function</label><label for='ays-answer-46-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['10'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <h3>A [very] little bit of history</h3>
<p>The Option key replaced the Closed Apple key () from pre-Macintosh Apple computers, for the same reason that the Open Apple key was replaced with the Command key &#8211; to reduce repetition of the Apple logo in the Mac GUI.</p>
<p>It seems to be unknown or at least undocumented as to how the symbol was chosen.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='9' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>4 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this symbol mean:</p>
<p><span style="font-size: 72px">⌃</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-42-1' value='42'/>

                <label for='ays-answer-42-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Option</label><label for='ays-answer-42-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-39-1' value='39'/>

                <label for='ays-answer-39-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Place of interest</label><label for='ays-answer-39-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-38-1' value='38'/>

                <label for='ays-answer-38-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Control</label><label for='ays-answer-38-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-40-1' value='40'/>

                <label for='ays-answer-40-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Function</label><label for='ays-answer-40-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-41-1' value='41'/>

                <label for='ays-answer-41-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Apple</label><label for='ays-answer-41-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-9]' id='ays-answer-37-1' value='37'/>

                <label for='ays-answer-37-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Command</label><label for='ays-answer-37-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['9'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <h3>A little history</h3>
<p>The use of a caret (also known as a chevron or circumflex) to symbolise the Control key appears to date back to <a href="http://bitsavers.org/pdf/dec/pdp6/PDP-6_TimsharingBroch.pdf#page=4" data-wpel-link="external" target="_blank" rel="external noopener">at least the PDP-6 era</a> (1963), where an upwards-pointing arrow was used.  <a href="https://www.sensitiveresearch.com/Archive/CharCodeHist/X3.4-1963/page5.JPG" data-wpel-link="external" target="_blank" rel="external noopener">That symbol was part of the very first ASCII standard</a> (<a href="https://www.sensitiveresearch.com/Archive/CharCodeHist/X3.4-1963/index.html" data-wpel-link="external" target="_blank" rel="external noopener">ASA standard X3.4-1963</a>) as 0x5E, but a few years later that character was changed to the now-familiar caret (I believe in X3.4-1967, although the closest I can find an actual copy of is <a href="https://archive.org/details/enf-ascii-1968-1970/page/n5/mode/2up" data-wpel-link="external" target="_blank" rel="external noopener">X3.4-1968</a>).  You can read <a href="https://www.aivosto.com/articles/charsets-7bit.html" data-wpel-link="external" target="_blank" rel="external noopener">the full history of ASCII</a>, if you&#8217;re interested.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='11' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>5 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this symbol mean:</p>
<p><span style="font-size: 72px">⇧</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-50-1' value='50'/>

                <label for='ays-answer-50-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Control</label><label for='ays-answer-50-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-49-1' value='49'/>

                <label for='ays-answer-49-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Command</label><label for='ays-answer-49-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-54-1' value='54'/>

                <label for='ays-answer-54-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Option</label><label for='ays-answer-54-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-51-1' value='51'/>

                <label for='ays-answer-51-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Shift</label><label for='ays-answer-51-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-52-1' value='52'/>

                <label for='ays-answer-52-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Function</label><label for='ays-answer-52-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-11]' id='ays-answer-53-1' value='53'/>

                <label for='ays-answer-53-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Apple</label><label for='ays-answer-53-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['11'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='3' data-type='radio'>
                    
                    <div class='ays_question_hint_container '>
                        <i class='ays_fa ays_fa_info_circle ays_question_hint ays-quiz-keyboard-active' tabindex='0' aria-hidden='true'></i>
                        <span class='ays_question_hint_text'><p>It&#8217;s been the same shortcut since the original Macintosh.</p>
</span>
                    </div>
                    
                    <p class='ays-question-counter animated'>6 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you take a fullscreen screen shot?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-3]' id='ays-answer-9-1' value='9'/>

                <label for='ays-answer-9-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘4</label><label for='ays-answer-9-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-3]' id='ays-answer-7-1' value='7'/>

                <label for='ays-answer-7-1' class='  ays_position_initial  ays-quiz-keyboard-label'>PrtScn</label><label for='ays-answer-7-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-3]' id='ays-answer-8-1' value='8'/>

                <label for='ays-answer-8-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘3</label><label for='ays-answer-8-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-3]' id='ays-answer-10-1' value='10'/>

                <label for='ays-answer-10-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘P</label><label for='ays-answer-10-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-3]' id='ays-answer-11-1' value='11'/>

                <label for='ays-answer-11-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘P</label><label for='ays-answer-11-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['3'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Fun fact:  Apple&#8217;s very first operating system technical note, <a href="https://developer.apple.com/library/archive/technotes/os/os_01.html" data-wpel-link="external" target="_blank" rel="external noopener">OS01</a>, was to call out the existence of command-shift-N shortcuts, including ⇧⌘3.</p>
<p>⇧⌘4 also existed from the beginning, although its original behaviour was to <em>print</em> the active window (or the whole screen if Caps Lock was enabled), rather than save to a file!</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='6' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>7 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you cycle through open applications (in order from most recently used to least)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-24-1' value='24'/>

                <label for='ays-answer-24-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘→</label><label for='ays-answer-24-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-25-1' value='25'/>

                <label for='ays-answer-25-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘]</label><label for='ays-answer-25-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-23-1' value='23'/>

                <label for='ays-answer-23-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃→</label><label for='ays-answer-23-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-22-1' value='22'/>

                <label for='ays-answer-22-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘⇥</label><label for='ays-answer-22-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-26-1' value='26'/>

                <label for='ays-answer-26-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘A</label><label for='ays-answer-26-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-6]' id='ays-answer-21-1' value='21'/>

                <label for='ays-answer-21-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘`</label><label for='ays-answer-21-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['6'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Tip: ⇧⌘⇥ cycles in the opposite direction, i.e. <em>least</em> recently used application first.</p>
<p>Pro tip: you can release the ⇥ key while still holding ⌘ to keep the application switcher on screen.  You can then hit ⇥ or ⇧⇥ successive times to move through the applications at your own pace.  Or interact with them via the mouse (including dropping things onto them, such as files to open).</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='13' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>8 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you minimise the active window?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-13]' id='ays-answer-62-1' value='62'/>

                <label for='ays-answer-62-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘H</label><label for='ays-answer-62-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-13]' id='ays-answer-64-1' value='64'/>

                <label for='ays-answer-64-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘W</label><label for='ays-answer-64-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-13]' id='ays-answer-63-1' value='63'/>

                <label for='ays-answer-63-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘C</label><label for='ays-answer-63-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-13]' id='ays-answer-61-1' value='61'/>

                <label for='ays-answer-61-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘M</label><label for='ays-answer-61-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['13'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='31' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>9 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you insert an ellipsis (…)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-31]' id='ays-answer-154-1' value='154'/>

                <label for='ays-answer-154-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥;</label><label for='ays-answer-154-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-31]' id='ays-answer-156-1' value='156'/>

                <label for='ays-answer-156-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌥.</label><label for='ays-answer-156-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-31]' id='ays-answer-157-1' value='157'/>

                <label for='ays-answer-157-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌥;</label><label for='ays-answer-157-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-31]' id='ays-answer-155-1' value='155'/>

                <label for='ays-answer-155-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥.</label><label for='ays-answer-155-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['31'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>In <em>some</em> applications you can also just type three periods followed by a space, and auto-replacement will turn the three periods into an actual ellipsis.  But this is not reliable across applications, even amongst native Mac apps.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='26' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>10 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this do in the Finder:</p>
<p><span style="font-size: 36px">⇧⌘A</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-26]' id='ays-answer-132-1' value='132'/>

                <label for='ays-answer-132-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Selects all files (but not folders)</label><label for='ays-answer-132-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-26]' id='ays-answer-131-1' value='131'/>

                <label for='ays-answer-131-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Selects all items</label><label for='ays-answer-131-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-26]' id='ays-answer-134-1' value='134'/>

                <label for='ays-answer-134-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Jumps to the Applications folder</label><label for='ays-answer-134-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-26]' id='ays-answer-135-1' value='135'/>

                <label for='ays-answer-135-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Creates a zip archive of the selected items</label><label for='ays-answer-135-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-26]' id='ays-answer-133-1' value='133'/>

                <label for='ays-answer-133-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Creates an alias of the select item(s)</label><label for='ays-answer-133-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['26'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='28' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>11 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>In a <em>native</em> Mac program, how do you hyperlink the selected text (where applicable)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-28]' id='ays-answer-142-1' value='142'/>

                <label for='ays-answer-142-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘K</label><label for='ays-answer-142-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-28]' id='ays-answer-143-1' value='143'/>

                <label for='ays-answer-143-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘U</label><label for='ays-answer-143-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-28]' id='ays-answer-140-1' value='140'/>

                <label for='ays-answer-140-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘L</label><label for='ays-answer-140-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-28]' id='ays-answer-141-1' value='141'/>

                <label for='ays-answer-141-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘H</label><label for='ays-answer-141-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['28'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='15' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>12 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What do these do:</p>
<p><span style="font-size: 36px">⌥⌘C / ⌥⌘V</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-73-1' value='73'/>

                <label for='ays-answer-73-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Copy / paste text, removing styling information</label><label for='ays-answer-73-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-75-1' value='75'/>

                <label for='ays-answer-75-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Cut text / paste and clear the clipboard</label><label for='ays-answer-75-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-71-1' value='71'/>

                <label for='ays-answer-71-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Copy / paste text styling</label><label for='ays-answer-71-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-72-1' value='72'/>

                <label for='ays-answer-72-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Copy / paste text</label><label for='ays-answer-72-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-76-1' value='76'/>

                <label for='ays-answer-76-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Clear / reset the current text field</label><label for='ays-answer-76-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-15]' id='ays-answer-74-1' value='74'/>

                <label for='ays-answer-74-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Nothing (beeps)</label><label for='ays-answer-74-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['15'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='8' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>13 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you type the  symbol?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-8]' id='ays-answer-33-1' value='33'/>

                <label for='ays-answer-33-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⇧K</label><label for='ays-answer-33-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-8]' id='ays-answer-35-1' value='35'/>

                <label for='ays-answer-35-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘A</label><label for='ays-answer-35-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-8]' id='ays-answer-34-1' value='34'/>

                <label for='ays-answer-34-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥A</label><label for='ays-answer-34-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-8]' id='ays-answer-36-1' value='36'/>

                <label for='ays-answer-36-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥5</label><label for='ays-answer-36-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['8'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='12' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>14 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you cycle through open windows within the active application?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-55-1' value='55'/>

                <label for='ays-answer-55-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘`</label><label for='ays-answer-55-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-58-1' value='58'/>

                <label for='ays-answer-58-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘→</label><label for='ays-answer-58-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-57-1' value='57'/>

                <label for='ays-answer-57-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃→</label><label for='ays-answer-57-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-60-1' value='60'/>

                <label for='ays-answer-60-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘A</label><label for='ays-answer-60-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-56-1' value='56'/>

                <label for='ays-answer-56-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘⇥</label><label for='ays-answer-56-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-12]' id='ays-answer-59-1' value='59'/>

                <label for='ays-answer-59-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘]</label><label for='ays-answer-59-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['12'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Tip: you can use ⇧⌘` to cycle through the windows in the reverse order.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='5' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>15 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you show the dictionary definition of the selected word (or phrase)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-5]' id='ays-answer-18-1' value='18'/>

                <label for='ays-answer-18-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘D</label><label for='ays-answer-18-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-5]' id='ays-answer-20-1' value='20'/>

                <label for='ays-answer-20-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Trick question: there is no such shortcut</label><label for='ays-answer-20-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-5]' id='ays-answer-19-1' value='19'/>

                <label for='ays-answer-19-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘D</label><label for='ays-answer-19-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-5]' id='ays-answer-17-1' value='17'/>

                <label for='ays-answer-17-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘D</label><label for='ays-answer-17-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['5'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Tip: Apple build in a large number of translating dictionaries, e.g. for Spanish to English.  They provide translations and definitions in your native language.  You can enable them in the Settings for the Dictionary app.</p>
<p>Pro tip: Apple include a special dictionary of proper nouns for Apple products, such as macOS releases.  Helpful if you can&#8217;t remember the name of the operating system you were running just last year. 😜</p>
<p><a href="https://wadetregaskis.com/wp-content/uploads/2024/01/Ventura-Definition-pop-up-1.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-7363" src="https://wadetregaskis.com/wp-content/uploads/2024/01/Ventura-Definition-pop-up-1.webp" alt="" width="462" height="478" srcset="https://wadetregaskis.com/wp-content/uploads/2024/01/Ventura-Definition-pop-up-1.webp 462w, https://wadetregaskis.com/wp-content/uploads/2024/01/Ventura-Definition-pop-up-1-247x256.webp 247w, https://wadetregaskis.com/wp-content/uploads/2024/01/Ventura-Definition-pop-up-1@2x.webp 924w" sizes="auto, (max-width: 462px) 100vw, 462px" /></a></p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='16' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>16 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>In a <em>native</em> Mac application, the Home key moves the focus point to the beginning of the…</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-16]' id='ays-answer-78-1' value='78'/>

                <label for='ays-answer-78-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Paragraph</label><label for='ays-answer-78-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-16]' id='ays-answer-81-1' value='81'/>

                <label for='ays-answer-81-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Word</label><label for='ays-answer-81-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-16]' id='ays-answer-79-1' value='79'/>

                <label for='ays-answer-79-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Document</label><label for='ays-answer-79-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-16]' id='ays-answer-80-1' value='80'/>

                <label for='ays-answer-80-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Page</label><label for='ays-answer-80-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-16]' id='ays-answer-77-1' value='77'/>

                <label for='ays-answer-77-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Line</label><label for='ays-answer-77-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['16'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Tip: in non-native applications which get this wrong, an alternative shortcut that they sometimes get right is ⌘↑.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='27' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>17 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>When clicking a disclosure triangle in a hierarchical list view, what modifier causes it to expand / contract the target item <em>and all its children recursively</em>?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-27]' id='ays-answer-139-1' value='139'/>

                <label for='ays-answer-139-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃</label><label for='ays-answer-139-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-27]' id='ays-answer-136-1' value='136'/>

                <label for='ays-answer-136-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥</label><label for='ays-answer-136-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-27]' id='ays-answer-137-1' value='137'/>

                <label for='ays-answer-137-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘</label><label for='ays-answer-137-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-27]' id='ays-answer-138-1' value='138'/>

                <label for='ays-answer-138-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧</label><label for='ays-answer-138-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['27'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='25' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>18 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you jump to your Documents folder in the Finder?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-25]' id='ays-answer-129-1' value='129'/>

                <label for='ays-answer-129-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘G</label><label for='ays-answer-129-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-25]' id='ays-answer-127-1' value='127'/>

                <label for='ays-answer-127-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘H</label><label for='ays-answer-127-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-25]' id='ays-answer-130-1' value='130'/>

                <label for='ays-answer-130-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘D</label><label for='ays-answer-130-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-25]' id='ays-answer-128-1' value='128'/>

                <label for='ays-answer-128-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘O</label><label for='ays-answer-128-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['25'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ' style='display:none'>
                            <p>Don&#8217;t feel too bad, I get this wrong <em>every single time</em>, too.  It&#8217;s an <em>incredibly</em> poor design choice, on Apple&#8217;s part, to use ⇧⌘D for the desktop folder.</p>

                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='21' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>19 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you cycle through find results in reverse order?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-21]' id='ays-answer-110-1' value='110'/>

                <label for='ays-answer-110-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥←</label><label for='ays-answer-110-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-21]' id='ays-answer-108-1' value='108'/>

                <label for='ays-answer-108-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘G</label><label for='ays-answer-108-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-21]' id='ays-answer-109-1' value='109'/>

                <label for='ays-answer-109-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘R</label><label for='ays-answer-109-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-21]' id='ays-answer-107-1' value='107'/>

                <label for='ays-answer-107-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥F</label><label for='ays-answer-107-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['21'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='30' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>20 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What is the traditional Mac way to cancel an operation?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-150-1' value='150'/>

                <label for='ays-answer-150-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘.</label><label for='ays-answer-150-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-151-1' value='151'/>

                <label for='ays-answer-151-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘S</label><label for='ays-answer-151-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-148-1' value='148'/>

                <label for='ays-answer-148-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃C</label><label for='ays-answer-148-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-152-1' value='152'/>

                <label for='ays-answer-152-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Brk</label><label for='ays-answer-152-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-149-1' value='149'/>

                <label for='ays-answer-149-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⎋</label><label for='ays-answer-149-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-30]' id='ays-answer-153-1' value='153'/>

                <label for='ays-answer-153-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌥⌫</label><label for='ays-answer-153-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['30'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ' style='display:none'>
                            <p>Don&#8217;t feel too bad &#8211; this isn&#8217;t a well-supported shortcut.  Since Mac OS X, ⎋ is sometimes used as well (bleeding over from Windows) and ⌃C likewise from Unix.</p>

                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='2' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>21 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What modifier key do you use to show a Spotlight file result in the Finder?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-2]' id='ays-answer-13-1' value='13'/>

                <label for='ays-answer-13-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Fn</label><label for='ays-answer-13-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-2]' id='ays-answer-4-1' value='4'/>

                <label for='ays-answer-4-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧</label><label for='ays-answer-4-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-2]' id='ays-answer-12-1' value='12'/>

                <label for='ays-answer-12-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃</label><label for='ays-answer-12-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-2]' id='ays-answer-6-1' value='6'/>

                <label for='ays-answer-6-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥</label><label for='ays-answer-6-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-2]' id='ays-answer-5-1' value='5'/>

                <label for='ays-answer-5-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘</label><label for='ays-answer-5-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['2'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='1' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>22 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this do:</p>
<p><span style="font-size: 36px">⌥⌘Space</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-1]' id='ays-answer-2-1' value='2'/>

                <label for='ays-answer-2-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Opens Spotlight</label><label for='ays-answer-2-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-1]' id='ays-answer-14-1' value='14'/>

                <label for='ays-answer-14-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Opens Control Centre</label><label for='ays-answer-14-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-1]' id='ays-answer-1-1' value='1'/>

                <label for='ays-answer-1-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Opens a Finder window with the search field selected</label><label for='ays-answer-1-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-1]' id='ays-answer-3-1' value='3'/>

                <label for='ays-answer-3-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Inserts a non-breaking space character</label><label for='ays-answer-3-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['1'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='14' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>23 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you record a video of the screen (or a subset thereof)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-66-1' value='66'/>

                <label for='ays-answer-66-1' class='  ays_position_initial  ays-quiz-keyboard-label'>No shortcut, only through Quicktime Player</label><label for='ays-answer-66-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-70-1' value='70'/>

                <label for='ays-answer-70-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘3 held down for at least one second</label><label for='ays-answer-70-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-65-1' value='65'/>

                <label for='ays-answer-65-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘5</label><label for='ays-answer-65-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-68-1' value='68'/>

                <label for='ays-answer-68-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⇧⌘R</label><label for='ays-answer-68-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-69-1' value='69'/>

                <label for='ays-answer-69-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘4, then hold M while selecting the target region</label><label for='ays-answer-69-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-14]' id='ays-answer-67-1' value='67'/>

                <label for='ays-answer-67-1' class='  ays_position_initial  ays-quiz-keyboard-label'>No shortcut, only through 3rd party programs</label><label for='ays-answer-67-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['14'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Strictly-speaking this doesn&#8217;t record <em>only</em> a video &#8211; it brings up a palette which lets you choose between stills &amp; video, among other configuration options.</p>
<p>This feature was introduced relatively recently, in macOS 10.14 Mojave (2018), as part of the addition of <a href="https://support.apple.com/en-au/guide/mac-help/mh26782/mac" data-wpel-link="external" target="_blank" rel="external noopener">the Screenshot app</a> to macOS.</p>
<p>Tip: similar to ⇧⌘4, ⇧⌘5 lets you choose a specific region of the screen.  However, unlike ⇧⌘4, your chosen region persists across invocations &#8211; making it trivial to capture the exact same region of your screen repeatedly.  Very convenient for marketing shots (e.g. light vs dark mode versions).</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='22' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>24 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>In a <em>native</em> Mac app, how do you toggle full screen mode?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-22]' id='ays-answer-113-1' value='113'/>

                <label for='ays-answer-113-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘⏎</label><label for='ays-answer-113-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-22]' id='ays-answer-114-1' value='114'/>

                <label for='ays-answer-114-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⏎</label><label for='ays-answer-114-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-22]' id='ays-answer-111-1' value='111'/>

                <label for='ays-answer-111-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘F</label><label for='ays-answer-111-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-22]' id='ays-answer-115-1' value='115'/>

                <label for='ays-answer-115-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘↑</label><label for='ays-answer-115-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-22]' id='ays-answer-112-1' value='112'/>

                <label for='ays-answer-112-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘F</label><label for='ays-answer-112-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['22'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='29' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>25 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>With the cursor at the start of a word, how do you select the word?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-29]' id='ays-answer-146-1' value='146'/>

                <label for='ays-answer-146-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⇧→</label><label for='ays-answer-146-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-29]' id='ays-answer-145-1' value='145'/>

                <label for='ays-answer-145-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⌘→</label><label for='ays-answer-145-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-29]' id='ays-answer-147-1' value='147'/>

                <label for='ays-answer-147-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⇧W</label><label for='ays-answer-147-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-29]' id='ays-answer-144-1' value='144'/>

                <label for='ays-answer-144-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧⇟</label><label for='ays-answer-144-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['29'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Option-arrows, the various modifications thereof, are handy text editing utilities.  For completeness:</p>
<p>⌥← / ⌥→ move the cursor to the start or end (respectively) of the word (in the given direction), jumping word-by-word as necessary.</p>
<p>⌥↑ / ⌥↓ move the cursor to the start or end (respectively) of the paragraph (in the given direction), jumping paragraph-by-paragraph as necessary.</p>
<p>⌘← / ⌘→ move the cursor to the start or end (respectively) of the current line (the equivalent of Home and End on some other platforms).</p>
<p>All of these can be combined with ⇧ to select text as the cursor moves.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='19' data-type='radio'>
                    
                    <div class='ays_question_hint_container '>
                        <i class='ays_fa ays_fa_info_circle ays_question_hint ays-quiz-keyboard-active' tabindex='0' aria-hidden='true'></i>
                        <span class='ays_question_hint_text'><p>It sometimes doesn&#8217;t work if you use a Windows keyboard.</p>
</span>
                    </div>
                    
                    <p class='ays-question-counter animated'>26 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>On a PowerPC or Intel Mac, what do you hold down after hitting the power button in order to enter recovery mode?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-99-1' value='99'/>

                <label for='ays-answer-99-1' class='  ays_position_initial  ays-quiz-keyboard-label'>D</label><label for='ays-answer-99-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-95-1' value='95'/>

                <label for='ays-answer-95-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘R</label><label for='ays-answer-95-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-102-1' value='102'/>

                <label for='ays-answer-102-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘V</label><label for='ays-answer-102-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-101-1' value='101'/>

                <label for='ays-answer-101-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘PR</label><label for='ays-answer-101-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-98-1' value='98'/>

                <label for='ays-answer-98-1' class='  ays_position_initial  ays-quiz-keyboard-label'>T</label><label for='ays-answer-98-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-96-1' value='96'/>

                <label for='ays-answer-96-1' class='  ays_position_initial  ays-quiz-keyboard-label'>The mouse / trackpad button</label><label for='ays-answer-96-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-97-1' value='97'/>

                <label for='ays-answer-97-1' class='  ays_position_initial  ays-quiz-keyboard-label'>R</label><label for='ays-answer-97-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-19]' id='ays-answer-100-1' value='100'/>

                <label for='ays-answer-100-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥</label><label for='ays-answer-100-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['19'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='18' data-type='radio'>
                    
                    <div class='ays_question_hint_container '>
                        <i class='ays_fa ays_fa_info_circle ays_question_hint ays-quiz-keyboard-active' tabindex='0' aria-hidden='true'></i>
                        <span class='ays_question_hint_text'><p>It&#8217;s not the same as on PPC and Intel Macs.</p>
</span>
                    </div>
                    
                    <p class='ays-question-counter animated'>27 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>On an Apple Silicon Mac, what do you hold down after hitting the power button in order to select the startup disk?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-87-1' value='87'/>

                <label for='ays-answer-87-1' class='  ays_position_initial  ays-quiz-keyboard-label'>The power button</label><label for='ays-answer-87-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-93-1' value='93'/>

                <label for='ays-answer-93-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⇧</label><label for='ays-answer-93-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-94-1' value='94'/>

                <label for='ays-answer-94-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘</label><label for='ays-answer-94-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-88-1' value='88'/>

                <label for='ays-answer-88-1' class='  ays_position_initial  ays-quiz-keyboard-label'>The mouse / trackpad button</label><label for='ays-answer-88-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-89-1' value='89'/>

                <label for='ays-answer-89-1' class='  ays_position_initial  ays-quiz-keyboard-label'>R</label><label for='ays-answer-89-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-92-1' value='92'/>

                <label for='ays-answer-92-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥</label><label for='ays-answer-92-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-91-1' value='91'/>

                <label for='ays-answer-91-1' class='  ays_position_initial  ays-quiz-keyboard-label'>D</label><label for='ays-answer-91-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-18]' id='ays-answer-90-1' value='90'/>

                <label for='ays-answer-90-1' class='  ays_position_initial  ays-quiz-keyboard-label'>T</label><label for='ays-answer-90-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['18'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='24' data-type='checkbox'>
                    
                    
                    <p class='ays-question-counter animated'>28 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>In the Finder, what can this do (depending on what is selected &#8211; choose all that apply):</p>
<p><span style="font-size: 36px">⌘R</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-122-1' value='122'/>

                <label for='ays-answer-122-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Moves the selected item(s) to the bin</label><label for='ays-answer-122-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-125-1' value='125'/>

                <label for='ays-answer-125-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Rotates the selected image(s) 90°</label><label for='ays-answer-125-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-121-1' value='121'/>

                <label for='ays-answer-121-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Renames the selected item(s)</label><label for='ays-answer-121-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-124-1' value='124'/>

                <label for='ays-answer-124-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Shows the target of the selected alias / symlink</label><label for='ays-answer-124-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-126-1' value='126'/>

                <label for='ays-answer-126-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Removes (hides) the file extension of the selected item(s)</label><label for='ays-answer-126-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='checkbox' name='ays_questions[ays-question-24]' id='ays-answer-123-1' value='123'/>

                <label for='ays-answer-123-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Refreshes the current window</label><label for='ays-answer-123-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['24'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Many a Mac newbie has accidentally rotated their images when they merely meant to rename them. 😕</p>
<h3>Sidenote</h3>
<p>Introduced in System 7, aliases are the Mac version of a Unix symlink or Windows shortcut.  They are still nominally supported to this day in macOS, although many applications &#8211; even otherwise well-written, native ones &#8211; do not handle them correctly.  Most Unix applications, such as almost everything you find through Terminal, will not resolve aliases to their targets, even when those same programs work correctly with symlinks and hardlinks.  Not because they explicitly support those either, but because in macOS aliases are <em>not</em> automatically resolved by the Unix file system APIs, and curious and unfortunate design choice on Apple&#8217;s part.</p>
<p>As a result, aliases were forced out of typical use by Mac OS X.  Today they are rare in the wild.</p>
<p>Which is a shame, because they were technically far superior to symlinks.  Rather than merely storing a brittle path, as symlinks do, they stored a detailed and partially-redundant set of identifiers, making them maintain their link even if the original is moved or renamed, for example.  Unfortunately, Apple broke this behaviour as of Mac OS X 10.2 Jaguar, further cementing the demise of the alias.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='20' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>29 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you bring up the Character Viewer (also known as the Character Picker, Unicode Picker, or Emoji Picker)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-20]' id='ays-answer-105-1' value='105'/>

                <label for='ays-answer-105-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘P</label><label for='ays-answer-105-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-20]' id='ays-answer-103-1' value='103'/>

                <label for='ays-answer-103-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘Space</label><label for='ays-answer-103-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-20]' id='ays-answer-106-1' value='106'/>

                <label for='ays-answer-106-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃E</label><label for='ays-answer-106-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-20]' id='ays-answer-104-1' value='104'/>

                <label for='ays-answer-104-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌥⇧⌘C</label><label for='ays-answer-104-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['20'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p><strong>Tip</strong>: make sure to explore its Settings (the ⋯⃝ pop-up menu button in the top-left corner) to see the full range of available character categories.  By default only a small handful are enabled.</p>
<p><a href="https://wadetregaskis.com/wp-content/uploads/2024/01/Character-Palette-Settings.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-7365" src="https://wadetregaskis.com/wp-content/uploads/2024/01/Character-Palette-Settings.webp" alt="Screenshot of the Character Palette Settings window." width="452" height="438" srcset="https://wadetregaskis.com/wp-content/uploads/2024/01/Character-Palette-Settings.webp 452w, https://wadetregaskis.com/wp-content/uploads/2024/01/Character-Palette-Settings-256x248.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/01/Character-Palette-Settings@2x.webp 904w" sizes="auto, (max-width: 452px) 100vw, 452px" /></a></p>
<p><strong>Note</strong>: on some Macs, Fn-E <em>also</em> works.  It is not listed here because it is not the canonical shortcut, only works on some Macs (those that have a Fn key), and only since about macOS Big Sur.  It also has a habitat of not working reliably even on Macs which meet both those criteria.</p>
<p><strong>Meta-trivia</strong>: in an earlier version of this quiz, Fn-E was actually listed as an option, but not accepted &#8211; whoops!  The quiz has since been corrected to not list it as an option, to avoid confusion.  Of the options given (and on many Macs) ⌃⌘Space is the only correct answer.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='17' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>30 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>In a text field, what does ⌃E do?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-17]' id='ays-answer-82-1' value='82'/>

                <label for='ays-answer-82-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Moves the cursor to the end of the line</label><label for='ays-answer-82-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-17]' id='ays-answer-84-1' value='84'/>

                <label for='ays-answer-84-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Ends editing (removes focus from the field)</label><label for='ays-answer-84-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-17]' id='ays-answer-86-1' value='86'/>

                <label for='ays-answer-86-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Erases the rest of the line</label><label for='ays-answer-86-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-17]' id='ays-answer-85-1' value='85'/>

                <label for='ays-answer-85-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Erases the current line</label><label for='ays-answer-85-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-17]' id='ays-answer-83-1' value='83'/>

                <label for='ays-answer-83-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Scrolls to the bottom</label><label for='ays-answer-83-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['17'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='23' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>31 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>What does this do:</p>
<p><span style="font-size: 36px">⌥Space</span></p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-23]' id='ays-answer-119-1' value='119'/>

                <label for='ays-answer-119-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Inserts a space character after the cursor</label><label for='ays-answer-119-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-23]' id='ays-answer-117-1' value='117'/>

                <label for='ays-answer-117-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Inserts a non-breaking space character</label><label for='ays-answer-117-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-23]' id='ays-answer-118-1' value='118'/>

                <label for='ays-answer-118-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Opens Control Centre</label><label for='ays-answer-118-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-23]' id='ays-answer-120-1' value='120'/>

                <label for='ays-answer-120-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Actives Spaces / Mission Control</label><label for='ays-answer-120-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-23]' id='ays-answer-116-1' value='116'/>

                <label for='ays-answer-116-1' class='  ays_position_initial  ays-quiz-keyboard-label'>Opens Spotlight</label><label for='ays-answer-116-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['23'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            <p>Tip: <em>lots</em> of input validation code is very badly written, especially in websites (as opposed to e.g. native apps).  You can often get around &#8220;no spaces&#8221; restrictions with the non-breaking space character.</p>

                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                        
                        <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none' value='Prev' />
                        
                        <i class="ays_fa ays_fa_arrow_right ays_next action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                        <input type='button' class='ays_next action-button ays-quiz-keyboard-active ' value='Next' />
                    </div>
                        
                    </div>
                </div><div class='step ' data-question-id='32' data-type='radio'>
                    
                    
                    <p class='ays-question-counter animated'>32 / 32</p>
                    <div class='ays-abs-fs'>
                        
                        <div class='ays_quiz_question'>
                                <p>How do you paste text as &#8220;plain text&#8221; (adopting the existing style at the insertion point, rather than any styling from the original cut or copied text)?</p>

                            </div>
                            
                        <div class='ays-quiz-answers ays_grid_view_container  '>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-32]' id='ays-answer-159-1' value='159'/>

                <label for='ays-answer-159-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⌘V</label><label for='ays-answer-159-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-32]' id='ays-answer-160-1' value='160'/>

                <label for='ays-answer-160-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌘V</label><label for='ays-answer-160-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-32]' id='ays-answer-161-1' value='161'/>

                <label for='ays-answer-161-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌃⌘V</label><label for='ays-answer-161-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div>
            <div class='ays-field ays_grid_view_item ays-quiz-keyboard-active' tabindex='0' >
                <input type='hidden' name='ays_answer_correct[]' value='0'/>

                <input type='radio' name='ays_questions[ays-question-32]' id='ays-answer-158-1' value='158'/>

                <label for='ays-answer-158-1' class='  ays_position_initial  ays-quiz-keyboard-label'>⌥⇧⌘V</label><label for='ays-answer-158-1' class='ays_answer_image ays_answer_image_class ays_empty_before_content'></label>

            </div><script>
            if(typeof window.quizOptions_1 === 'undefined'){
                window.quizOptions_1 = [];
            }
            window.quizOptions_1['32'] = 'W10=';</script></div>                        
                        
                        
                        
                        
                        <div class='wrong_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='right_answer_text ays_do_not_show' style='display:none'>
                            
                        </div>
                        <div class='ays_questtion_explanation' style='display:none'>
                            
                        </div>
                        <div class='ays-quiz-additonal-box'>
                            
                        </div>   
                        <div class='ays_buttons_div'>
                            
                            <i class="ays_fa ays_fa_arrow_left ays_previous action-button ays_arrow ays-quiz-keyboard-active ays_display_none" tabindex='0'></i>
                            <input type='button' class='ays_previous action-button ays-quiz-keyboard-active ays_display_none'  value='Prev' />
                            <i class='ays_display_none ays_fa ays_fa_flag_checkered ays_finish action-button ays_arrow ays_next_arrow ays-quiz-keyboard-active' tabindex='0'></i><input type='submit' name='ays_finish_quiz' class='  ays_next ays_finish action-button ays-quiz-keyboard-active' value='Next'/>
                        </div>
                        
                    </div>
                </div><div class='step ays_thank_you_fs'>
            <div class='ays-abs-fs ays-end-page'><div data-class='lds-ellipsis' data-role='loader' class='ays-loader'><div></div><div></div><div></div><div></div></div><div class='ays_quiz_results_page'><div class='ays_score_message'></div><div class='ays_message'></div><p class='ays_restart_button_p'></p></div>
            </div>
        </div><style>
            div#ays-quiz-container-1 * {
                box-sizing: border-box;
            }

            #ays-quiz-container-1 [id^='ays_finish_quiz_'] div.step div.ays-abs-fs {
                width: 90%;
            }

            /* Styles for Internet Explorer start */
            #ays-quiz-container-1 #ays_finish_quiz_1 {
                
            }

            /* Styles for Quiz container */
            #ays-quiz-container-1{
                min-height: 450px;
                width:100%;
                background-color:#fff;
                background-position:center center;border-radius:8px;box-shadow: 0px 0px 50px  1px rgba(86,86,86,0.4);border: none;}

            /* Styles for questions */
            #ays-quiz-container-1 #ays_finish_quiz_1 div.step {
                min-height: 450px;
            }

            /* Styles for text inside quiz container */
            #ays-quiz-container-1 .ays-start-page *:not(input):not([class^='enlighter']),
            #ays-quiz-container-1 .ays_question_hint,
            #ays-quiz-container-1 label[for^="ays-answer-"],
            #ays-quiz-container-1 #ays_finish_quiz_1 p,
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-fs-title,
            #ays-quiz-container-1 .ays-fs-subtitle,
            #ays-quiz-container-1 .logged_in_message,
            #ays-quiz-container-1 .ays_score_message,
            #ays-quiz-container-1 .ays_message{
               color: #000000;
               outline: none;
            }

            div#ays-quiz-container-1 .ays_question_hint.ays-quiz-keyboard-active,
            div#ays-quiz-container-1 .ays_restart_button.ays-quiz-keyboard-active {
                outline: revert;
            }

            #ays-quiz-container-1 .ays-quiz-password-message-box,
            #ays-quiz-container-1 .ays-quiz-question-note-message-box,
            #ays-quiz-container-1 .ays_quiz_question,
            #ays-quiz-container-1 .ays-quiz-question-category-box,
            #ays-quiz-container-1 .ays_quiz_question *:not([class^='enlighter']) {
                color: #000000;
            }

            #ays-quiz-container-1 .ays-quiz-question-category-box em {
                color: #000000;
            }

            #ays-quiz-container-1 textarea,
            #ays-quiz-container-1 input::first-letter,
            #ays-quiz-container-1 select::first-letter,
            #ays-quiz-container-1 option::first-letter {
                color: initial !important;
            }
            
            #ays-quiz-container-1 p::first-letter:not(.ays_no_questions_message) {
                color: #000000 !important;
                background-color: transparent !important;
                font-size: inherit !important;
                font-weight: inherit !important;
                float: none !important;
                line-height: inherit !important;
                margin: 0 !important;
                padding: 0 !important;
            }
            
            
            #ays-quiz-container-1 .select2-container,
            #ays-quiz-container-1 .ays-field * {
                font-size: 16px !important;
            }
    
            #ays-quiz-container-1 .ays_quiz_question p {
                font-size: 16px;
                text-align: inherit;
            }

            #ays-quiz-container-1 .ays-fs-subtitle p {
                text-align:  center ;
            }

            #ays-quiz-container-1 .ays_quiz_question {
                text-align:  center ;
                margin-bottom: 10px;
            }

            #ays-quiz-container-1 .ays_quiz_question pre {
                max-width: 100%;
                white-space: break-spaces;
            }

            /* Mango theme (Version: 1.4.202301041836) | tid=32449 | Start */
            #ays-quiz-container-1 .ays_quiz_question img {
                float: unset;
                margin: 0;
            }
            /* Mango theme (Version: 1.4.202301041836) | End */

            #ays-quiz-container-1 .ays-quiz-timer p {
                font-size: 16px;
            }

            #ays-quiz-container-1 section.ays_quiz_redirection_timer_container hr,
            #ays-quiz-container-1 section.ays_quiz_timer_container hr {
                margin: 0;
            }

            #ays-quiz-container-1 section.ays_quiz_timer_container.ays_quiz_timer_red_warning .ays-quiz-timer {
                color: #ff0000;
            }

            #ays-quiz-container-1 .ays_thank_you_fs p {
                text-align: center;
            }

            #ays-quiz-container-1 .ays_quiz_results_page .ays_score span {
                visibility: visible;
            }

            #ays-quiz-container-1 input[type='button'],
            #ays-quiz-container-1 input[type='submit'] {
                color: #ffffff !important;
            }

            #ays-quiz-container-1 input[type='button']{
                outline: none;
            }

            #ays-quiz-container-1 .information_form input[type='text'],
            #ays-quiz-container-1 .information_form input[type='url'],
            #ays-quiz-container-1 .information_form input[type='number'],
            #ays-quiz-container-1 .information_form input[type='email'],
            #ays-quiz-container-1 .information_form input[type='checkbox'],
            #ays-quiz-container-1 .information_form input[type='tel'],
            #ays-quiz-container-1 .information_form textarea,
            #ays-quiz-container-1 .information_form select,
            #ays-quiz-container-1 .information_form option {
                color: initial !important;
                outline: none;
                background-image: unset;
            }

            #ays-quiz-container-1 .wrong_answer_text{
                color:#ff4d4d;
            }
            #ays-quiz-container-1 .right_answer_text{
                color:#33cc33;
            }

            #ays-quiz-container-1 .wrong_answer_text p {
                font-size:16px;
            }

            #ays-quiz-container-1 .ays_questtion_explanation p {
                font-size:16px;
            }

            #ays-quiz-container-1 .wrong_answer_text *:not(strong) {
                text-transform:none;
                text-decoration: none;
                letter-spacing: 0px;
                font-weight: normal;
            }

            #ays-quiz-container-1 .ays_questtion_explanation *:not(strong) {
                text-transform:none;
                text-decoration: none;
                letter-spacing: 0px;
                font-weight: normal;
            }

            #ays-quiz-container-1 .right_answer_text *:not(strong) {
                text-transform:none;
                text-decoration: none;
                letter-spacing: 0px;
                font-weight: normal;
            }

            #ays-quiz-container-1 .right_answer_text p {
                font-size:16px;
            }

            #ays-quiz-container-1 .ays-quiz-question-note-message-box p {
                font-size:14px;
            }

            #ays-quiz-container-1 .ays-quiz-question-note-message-box *:not(strong) {
                text-transform:none;
                text-decoration: none;
                letter-spacing: 0px;
                font-weight: normal;
            }
            
            #ays-quiz-container-1 .ays_cb_and_a,
            #ays-quiz-container-1 .ays_cb_and_a * {
                color: rgb(0,0,0);
                text-align: center;
            }

            /* Quiz textarea height */
            #ays-quiz-container-1 textarea {
                height: 100px;
                min-height: 100px;
            }

            /* Quiz rate and passed users count */
            #ays-quiz-container-1 .ays_quizn_ancnoxneri_qanak,
            #ays-quiz-container-1 .ays_quiz_rete_avg {
                color:#fff !important;
                background-color:#000000;   
            }

            #ays-quiz-container-1 .ays-questions-container > .ays_quizn_ancnoxneri_qanak {
                padding: 5px 20px;
            }
            #ays-quiz-container-1 div.for_quiz_rate.ui.star.rating .icon {
                color: rgba(0,0,0,0.35);
            }
            #ays-quiz-container-1 .ays_quiz_rete_avg div.for_quiz_rate_avg.ui.star.rating .icon {
                color: rgba(255,255,255,0.5);
            }

            #ays-quiz-container-1 .ays_quiz_rete .ays-quiz-rate-link-box .ays-quiz-rate-link {
                color: #000000;
            }

            /* Loaders */            
            #ays-quiz-container-1 div.lds-spinner,
            #ays-quiz-container-1 div.lds-spinner2 {
                color: #000000;
            }
            #ays-quiz-container-1 div.lds-spinner div:after,
            #ays-quiz-container-1 div.lds-spinner2 div:after {
                background-color: #000000;
            }
            #ays-quiz-container-1 .lds-circle,
            #ays-quiz-container-1 .lds-facebook div,
            #ays-quiz-container-1 .lds-ellipsis div{
                background: #000000;
            }
            #ays-quiz-container-1 .lds-ripple div{
                border-color: #000000;
            }
            #ays-quiz-container-1 .lds-dual-ring::after,
            #ays-quiz-container-1 .lds-hourglass::after{
                border-color: #000000 transparent #000000 transparent;
            }

            /* Stars */
            #ays-quiz-container-1 .ui.rating .icon,
            #ays-quiz-container-1 .ui.rating .icon:before {
                font-family: Rating !important;
            }

            /* Progress bars */
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-progress {
                border-color: rgba(0,0,0,0.8);
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-progress-bg {
                background-color: rgba(0,0,0,0.3);
            }    
            #ays-quiz-container-1 .ays-progress-value {
                color: #000000;
                text-align: center;
            }
            #ays-quiz-container-1 .ays-progress-bar {
                background-color: #5d6cf9;
            }
            #ays-quiz-container-1 .ays-question-counter .ays-live-bar-wrap {
                direction:ltr !important;
            }
            #ays-quiz-container-1 .ays-live-bar-fill{
                color: #000000;
                border-bottom: 2px solid rgba(0,0,0,0.8);
                text-shadow: 0px 0px 5px #fff;
            }
            #ays-quiz-container-1 .ays-live-bar-fill.ays-live-fourth,
            #ays-quiz-container-1 .ays-live-bar-fill.ays-live-third,
            #ays-quiz-container-1 .ays-live-bar-fill.ays-live-second {
                text-shadow: unset;
            }
            #ays-quiz-container-1 .ays-live-bar-percent{
                display:none;
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays_average {
                text-align: center;
            }
            
            /* Music, Sound */
            #ays-quiz-container-1 .ays_music_sound {
                color:rgb(0,0,0);
            }

            /* Dropdown questions scroll bar */
            #ays-quiz-container-1 blockquote {
                border-left-color: #000000 !important;                                      
            }

            /* Quiz Password */
            #ays-quiz-container-1 .ays-start-page > input[id^='ays_quiz_password_val_'],
            #ays-quiz-container-1 .ays-quiz-password-toggle-visibility-box {
                width: 100%;
            }


            /* Question hint */
            #ays-quiz-container-1 .ays_question_hint_container .ays_question_hint_text {
                background-color:#fff;
                box-shadow: 0 0 15px 3px rgba(86,86,86,0.6);
                max-width: 270px;
            }

            #ays-quiz-container-1 .ays_question_hint_container .ays_question_hint_text p {
                max-width: unset;
            }

            #ays-quiz-container-1 .ays-quiz-additonal-box {
                display: flex;
                justify-content: flex-end;
                align-items: center;
                position: relative;  
            }

            #ays-quiz-container-1 .ays_questions_hint_max_width_class {
                max-width: 80%;
            }

            /* Information form */
            #ays-quiz-container-1 .ays-form-title{
                color:rgb(0,0,0);
            }

            /* Quiz timer */
            #ays-quiz-container-1 div.ays-quiz-redirection-timer,
            #ays-quiz-container-1 div.ays-quiz-timer{
                color: #000000;
                text-align: center;
            }

            #ays-quiz-container-1 div.ays-quiz-timer.ays-quiz-message-before-timer:before {
                font-weight: 500;
            }

            /* Quiz title / transformation */
            #ays-quiz-container-1 .ays-fs-title{
                text-transform: none;
                font-size: 28px;
                text-align: center;
                    text-shadow: 2px 2px 3px  #cecece;
            }
            
            /* Quiz buttons */
            #ays-quiz-container-1 .ays_arrow {
                color:#ffffff!important;
            }
            #ays-quiz-container-1 input#ays-submit,
            #ays-quiz-container-1 #ays_finish_quiz_1 .action-button,
            div#ays-quiz-container-1 #ays_finish_quiz_1 .action-button.ays_restart_button,
            #ays-quiz-container-1 + .ays-quiz-category-selective-main-container .ays-quiz-category-selective-restart-bttn,
            #ays-quiz-container-1 .ays-quiz-category-selective-submit-bttn {
                background: none;
                background-color: #5d6cf9;
                color:#ffffff;
                font-size: 20px;
                padding: 13px 30px;
                border-radius: 8px;
                height: auto;
                letter-spacing: 0;
                box-shadow: unset;
                width: auto;
                /* border: unset; */
                min-height: unset;
                line-height: normal;
                text-shadow: unset;
            }
            #ays-quiz-container-1 input#ays-submit,
            #ays-quiz-container-1 #ays_finish_quiz_1 input.action-button,
            #ays-quiz-container-1 + .ays-quiz-category-selective-main-container .ays-quiz-category-selective-restart-bttn,
            #ays-quiz-container-1 .ays-quiz-category-selective-submit-bttn {
                
            }

            #ays-quiz-container-1 #ays_finish_quiz_1 .action-button.ays_check_answer {
                padding: 5px 10px;
                font-size: 20px !important;
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .action-button.ays_restart_button {
                white-space: nowrap;
                padding: 5px 10px;
                white-space: normal;
            }
            #ays-quiz-container-1 input#ays-submit:hover,
            #ays-quiz-container-1 input#ays-submit:focus,
            #ays-quiz-container-1 #ays_finish_quiz_1 .action-button:hover,
            #ays-quiz-container-1 #ays_finish_quiz_1 .action-button:focus,
            #ays-quiz-container-1 + .ays-quiz-category-selective-main-container .ays-quiz-category-selective-restart-bttn:hover,
            #ays-quiz-container-1 .ays-quiz-category-selective-submit-bttn:hover {
                background: none;
                box-shadow: 0 0 0 2px #ffffff;
                background-color: #5d6cf9;
            }
            #ays-quiz-container-1 .ays_restart_button {
                color: #ffffff;
            }
            
            #ays-quiz-container-1 .ays_restart_button_p,
            #ays-quiz-container-1 .ays_buttons_div {
                justify-content: center;
            }

            #ays-quiz-container-1 .ays_finish.action-button{
                margin: 10px 5px;
            }

            #ays-quiz-container-1 .ays-share-btn.ays-share-btn-branded {
                color: #fff;
                display: inline-block;
            }

            #ays-quiz-container-1 .ays_quiz_results .ays-field.checked_answer_div.correct_div input:checked+label {
                background-color: transparent;
            }
                        
            /* Question answers */
            #ays-quiz-container-1 .ays-field {
                    border-color: #dddddd;
                    border-style: solid;
                    border-width: 1px;
                    box-shadow: none;flex-direction: column-reverse;
            }
            

            /* Answer maximum length of a text field */
            #ays-quiz-container-1 .ays_quiz_question_text_message{
                color: #000000;
                text-align: left;
                font-size: 12px;
            }

            div#ays-quiz-container-1 div.ays_quiz_question_text_error_message {
                color: #ff0000;
            }

            /* Questions answer image */
            #ays-quiz-container-1 .ays-answer-image {
                width:100%;
                height:150px;
                object-fit: cover;
            }
            
            #ays-quiz-container-1 .ays-quiz-answers .ays-field:hover:not(.ays-answered-text-input){
                opacity: 1;
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field label.ays_answer_caption[for^='ays-answer-'] {
                z-index: 1;
                position:initial;bottom:0;}
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field input~label[for^='ays-answer-'] {
                padding: 5px;
            }

            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field {
                margin-bottom: 12px;
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field.ays_grid_view_item {
                width: calc(50% - 6px);
            }
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field.ays_grid_view_item:nth-child(odd) {
                margin-right: 6px;
            }
            
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field input:checked+label:before {
                border-color: #5d6cf9;
                background: #5d6cf9;
                background-clip: content-box;
            }
            #ays-quiz-container-1 .ays-quiz-answers div.ays-text-right-answer {
                color: #000000;
            }
                        
            /* Questions answer right/wrong icons */
            #ays-quiz-container-1 .ays-field input~label.answered.correct:after{
                content: url('https://wadetregaskis.com/wp-content/plugins/quiz-maker/public/images/correct.png');          }
            #ays-quiz-container-1 .ays-field input~label.answered.wrong:after{
                content: url('https://wadetregaskis.com/wp-content/plugins/quiz-maker/public/images/wrong.png');
            }
            #ays-quiz-container-1 .ays-field label.answered:last-of-type:after{
                display: none;
                height: auto;
                left: 10px;top: 10px;}
            /* Dropdown questions */            
            #ays-quiz-container-1 #ays_finish_quiz_1 .ays-field .select2-container--default .select2-selection--single {
                border-bottom: 2px solid #5d6cf9;
                background-color: #5d6cf9;
            }
            
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__placeholder,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__rendered,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__arrow {
                color: #ffffff;
            }

            #ays-quiz-container-1 .select2-container--default .select2-search--dropdown .select2-search__field:focus,
            #ays-quiz-container-1 .select2-container--default .select2-search--dropdown .select2-search__field {
                outline: unset;
                padding: 0.75rem;
            }

            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__rendered,
            #ays-quiz-container-1 .select2-container--default .select2-results__option--highlighted[aria-selected] {
                background-color: #5d6cf9;
            }

            #ays-quiz-container-1 .ays-field .select2-container--default,
            #ays-quiz-container-1 .ays-field .select2-container--default .selection,
            #ays-quiz-container-1 .ays-field .select2-container--default .dropdown-wrapper,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__rendered,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__rendered .select2-selection__placeholder,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__arrow,
            #ays-quiz-container-1 .ays-field .select2-container--default .select2-selection--single .select2-selection__arrow b[role='presentation'] {
                font-size: 16px !important;
            }

            #ays-quiz-container-1 .select2-container--default .select2-results__option {
                padding: 6px;
            }
            
            /* Dropdown questions scroll bar */
            #ays-quiz-container-1 .select2-results__options::-webkit-scrollbar {
                width: 7px;
            }
            #ays-quiz-container-1 .select2-results__options::-webkit-scrollbar-track {
                background-color: rgba(255,255,255,0.35);
            }
            #ays-quiz-container-1 .select2-results__options::-webkit-scrollbar-thumb {
                transition: .3s ease-in-out;
                background-color: rgba(0,0,0,0.55);
            }
            #ays-quiz-container-1 .select2-results__options::-webkit-scrollbar-thumb:hover {
                transition: .3s ease-in-out;
                background-color: rgba(0,0,0,0.85);
            }

            /* Audio / Video */
            #ays-quiz-container-1 .mejs-container .mejs-time{
                box-sizing: unset;
            }
            #ays-quiz-container-1 .mejs-container .mejs-time-rail {
                padding-top: 15px;
            }

            #ays-quiz-container-1 .mejs-container .mejs-mediaelement video {
                margin: 0;
            }

            /* Limitation */
            #ays-quiz-container-1 .ays-quiz-limitation-count-of-takers {
                padding: 50px;
            }

            #ays-quiz-container-1 div.ays-quiz-results-toggle-block span.ays-show-res-toggle.ays-res-toggle-show,
            #ays-quiz-container-1 div.ays-quiz-results-toggle-block span.ays-show-res-toggle.ays-res-toggle-hide{
                color: #000000;
            }

            #ays-quiz-container-1 div.ays-quiz-results-toggle-block input:checked + label.ays_switch_toggle {
                border: 1px solid #000000;
            }

            #ays-quiz-container-1 div.ays-quiz-results-toggle-block input:checked + label.ays_switch_toggle {
                border: 1px solid #000000;
            }

            #ays-quiz-container-1 div.ays-quiz-results-toggle-block input:checked + label.ays_switch_toggle:after{
                background: #000000;
            }

            #ays-quiz-container-1.ays_quiz_elegant_dark div.ays-quiz-results-toggle-block input:checked + label.ays_switch_toggle:after,
            #ays-quiz-container-1.ays_quiz_rect_dark div.ays-quiz-results-toggle-block input:checked + label.ays_switch_toggle:after{
                background: #000;
            }

            /* Hestia theme (Version: 3.0.16) | Start */
            #ays-quiz-container-1 .mejs-container .mejs-inner .mejs-controls .mejs-button > button:hover,
            #ays-quiz-container-1 .mejs-container .mejs-inner .mejs-controls .mejs-button > button {
                box-shadow: unset;
                background-color: transparent;
            }
            #ays-quiz-container-1 .mejs-container .mejs-inner .mejs-controls .mejs-button > button {
                margin: 10px 6px;
            }
            /* Hestia theme (Version: 3.0.16) | End */

            /* Go theme (Version: 1.4.3) | Start */
            #ays-quiz-container-1 label[for^='ays-answer']:before,
            #ays-quiz-container-1 label[for^='ays-answer']:before {
                -webkit-mask-image: unset;
                mask-image: unset;
            }

            #ays-quiz-container-1.ays_quiz_classic_light .ays-field input:checked+label.answered.correct:before,
            #ays-quiz-container-1.ays_quiz_classic_dark .ays-field input:checked+label.answered.correct:before {
                background-color: #5d6cf9 !important;
                background-color: rgba(39,174,96, 1) !important;
            }
            /* Go theme (Version: 1.4.3) | End */

            #ays-quiz-container-1 .ays_quiz_results fieldset.ays_fieldset .ays_quiz_question .wp-video {
                width: 100% !important;
                max-width: 100%;
            }

            /* Classic Dark / Classic Light */
            /* Dropdown questions right/wrong styles */
            #ays-quiz-container-1.ays_quiz_classic_dark .correct_div,
            #ays-quiz-container-1.ays_quiz_classic_light .correct_div{
                border-color:green !important;
                opacity: 1 !important;
                background-color: rgba(39,174,96,0.4) !important;
            }
            #ays-quiz-container-1.ays_quiz_classic_dark .correct_div .selected-field,
            #ays-quiz-container-1.ays_quiz_classic_light .correct_div .selected-field {
                padding: 0px 10px 0px 10px;
                color: green !important;
            }

            #ays-quiz-container-1.ays_quiz_classic_dark .wrong_div,
            #ays-quiz-container-1.ays_quiz_classic_light .wrong_div{
                border-color:red !important;
                opacity: 1 !important;
                background-color: rgba(243,134,129,0.4) !important;
            }
            #ays-quiz-container-1.ays_quiz_classic_dark .ays-field.checked_answer_div.wrong_div input:checked~label,
            #ays-quiz-container-1.ays_quiz_classic_light .ays-field.checked_answer_div.wrong_div input:checked~label {
                background-color: rgba(243,134,129,0.4) !important;
            }
            #ays-quiz-container-1.ays_quiz_classic_dark .ays-field,
            #ays-quiz-container-1.ays_quiz_classic_light .ays-field {
                text-align: left;
                /*margin-bottom: 10px;*/
                padding: 0;
                transition: .3s ease-in-out;
            }

            #ays-quiz-container-1 .ays-quiz-close-full-screen {
                fill: #000000;
            }

            #ays-quiz-container-1 .ays-quiz-open-full-screen {
                fill: #000000;
            }

            #ays-quiz-container-1 .ays_quiz_login_form p{
                color: #000000;
            }

            /* report questions modal start */
            .ays-modal-reports {
                display: none;
                position: fixed;
                z-index: 9999;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                overflow: auto;
                background-color: rgba(0, 0, 0, 0.4);
                animation-duration: .5s;
                background-color: rgba(0, 0, 0, 0.4);
            }

            #ays-quiz-question-report-modal-1 .ays-modal-content-reports {
                background-color: #fefefe;
                margin: 10% auto;
                border: 1px solid #888;
                max-width: 500px;
                width: 100%;
                border-radius: 10px;
                padding: 20px;
                box-sizing: border-box;
                position: relative;
            }

            #ays-quiz-question-report-modal-1 .ays-modal-content-reports label{
                display: block;
                margin-bottom: 10px;
                font-weight: bold;
                letter-spacing: normal;
            }

            #ays-quiz-question-report-modal-1 .ays-modal-content-reports textarea {
                display: block;
                width: 100%;
                height: 100px;
                padding: 5px;
                box-sizing: border-box;
                border-radius: 5px;
                border: 1px solid #ccc;
                max-width: 100%;
                max-height: 500px;
                resize: vertical;
                transition: unset;
            }

            #ays-quiz-question-report-modal-1 .ays-modal-content-reports input.ays-quiz-submit-question-report {
                background-color: #0073aa;
                color: #fff !important;
                border: none;
                border-radius: 5px;
                padding: 10px 20px;
                cursor: pointer;
                margin-top: 20px;
                line-height: normal;
                letter-spacing: normal;
                box-shadow: unset;
                background-image: unset;
            }

            #ays-quiz-question-report-modal-1 .ays-close-reports-window {
                color: #aaa;
                font-weight: bold;
                position: absolute;
                top: 1%;
                right: 1%;
            }

            #ays-quiz-question-report-modal-1 .ays-close-reports-window img {
                box-shadow: unset;
            }
            
            #ays-quiz-question-report-modal-1 .ays-quiz-preloader img {
                box-shadow: unset;
            }

            #ays-quiz-question-report-modal-1 .ays-close-reports-window img:hover,
            #ays-quiz-question-report-modal-1 .ays-close-reports-window img:focus {
                cursor: pointer;
            }

            #ays-quiz-question-report-modal-1 .ays-quiz-question-report-error {
                display: none;
                font-size: 13px;
                color: #f00;
                text-align: left;
            }

            #ays-quiz-question-report-modal-1 .ays-quiz-question-report-textarea-label,
            #ays-quiz-question-report-modal-1 .ays-quiz-question-report-title {
                text-align: left;
            }

            #ays-quiz-question-report-modal-1 .ays_quiz_modal_overlay {
                width: 100%;
                height: 100%;
                position: fixed;
                top: 0;
                left: 0;
                position: absolute;
                z-index: 1000000000;
                flex-direction: column;
            }

            #ays-quiz-question-report-modal-1 .ays-quiz-container .ays-quiz-user-cհoosing-anonymous-assessment {
                margin: 10px 0;
            }

            /* report questions modal end */

            @media screen and (max-width: 768px){
                #ays-quiz-container-1{
                    max-width: 100%;
                }

                div#ays-quiz-container-1 [id^='ays_finish_quiz_'] div.step div.ays-abs-fs {
                    width: 90%;
                }

                #ays-quiz-container-1 .ays_quiz_question p {
                    font-size: 16px;
                }

                #ays-quiz-container-1 .select2-container,
                #ays-quiz-container-1 .ays-field * {
                    font-size: 15px !important;
                }

                div#ays-quiz-container-1 input#ays-submit,
                div#ays-quiz-container-1 #ays_finish_quiz_1 .action-button,
                div#ays-quiz-container-1 #ays_finish_quiz_1 .action-button.ays_restart_button,
                #ays-quiz-container-1 + .ays-quiz-category-selective-main-container .ays-quiz-category-selective-restart-bttn,
                #ays-quiz-container-1 .ays-quiz-category-selective-submit-bttn {
                    font-size: 20px;
                }

                /* Quiz title / mobile font size */
                div#ays-quiz-container-1 .ays-fs-title {
                    font-size: 20px;
                }

                /* Question explanation / mobile font size */
                #ays-quiz-container-1 .ays_questtion_explanation p {
                    font-size:16px;
                }

                /* Wrong answers / mobile font size */
                #ays-quiz-container-1 .wrong_answer_text p {
                    font-size:16px;
                }

                /* Right answers / mobile font size */
                #ays-quiz-container-1 .right_answer_text p {
                    font-size:16px;
                }

                /* Note text / mobile font size */
                #ays-quiz-container-1 .ays-quiz-question-note-message-box p {
                    font-size:14px;
                }

                div#ays-quiz-container-1 .ays-quiz-question-note-message-box *:not(strong) {
                    text-transform:none;
                    text-decoration: none;
                    letter-spacing: 0px;
                    font-weight: normal;
                }

                div#ays-quiz-container-1 .ays_questtion_explanation *:not(strong) {
                    text-transform:none;
                    text-decoration: none;
                    letter-spacing: 0px;
                    font-weight: normal;
                }

                div#ays-quiz-container-1 .right_answer_text *:not(strong) {
                    text-transform:none;
                    text-decoration: none;
                    letter-spacing: 0px;
                    font-weight: normal;
                }

                div#ays-quiz-container-1 .wrong_answer_text *:not(strong) {
                    text-transform:none;
                    text-decoration: none;
                    letter-spacing: 0px;
                    font-weight: normal;
                }
            }
            /* Custom css styles */
            
            
            /* RTL direction styles */
            
        </style>
            <style>
                #ays-quiz-container-1 p {
                    margin: 0.625em;
                }
                
                #ays-quiz-container-1 .ays-field.checked_answer_div input:checked~label {
                    background-color: rgba(93,108,249,0.6);
                }

                #ays-quiz-container-1.ays_quiz_classic_light  .ays_quiz_results .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_dark  .ays_quiz_results .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_light  .enable_correction .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_dark  .enable_correction .ays-field.checked_answer_div input:checked~label {
                    background-color: transparent;
                }

                #ays-quiz-container-1.ays_quiz_classic_light  .ays_quiz_results .not_influence_to_score .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_dark  .ays_quiz_results .not_influence_to_score .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_light  .enable_correction .not_influence_to_score .ays-field.checked_answer_div input:checked~label,
                #ays-quiz-container-1.ays_quiz_classic_dark  .enable_correction .not_influence_to_score .ays-field.checked_answer_div input:checked~label {
                    background-color: rgba(93,108,249,0.6);
                }
                
                #ays-quiz-container-1 .ays-field.checked_answer_div input:checked~label:hover {
                    background-color: rgba(93,108,249,0.8);
                }

                #ays-quiz-container-1.ays-quiz-container.ays_quiz_classic_light .ays-questions-container .ays-field:hover label[for^='ays-answer-'],
                #ays-quiz-container-1 .ays-field:hover:not(.ays-answered-text-input){
                    background: rgba(93,108,249,0.8);
                    /* border-radius: 4px; */
                    color: #fff;
                    transition: all .3s;
                }
                #ays-quiz-container-1 #ays_finish_quiz_1 .action-button:hover,
                #ays-quiz-container-1 #ays_finish_quiz_1 .action-button:focus,
                #ays-quiz-container-1 + .ays-quiz-category-selective-main-container .ays-quiz-category-selective-restart-bttn:hover,
                #ays-quiz-container-1 .ays-quiz-category-selective-submit-bttn:focus {
                    box-shadow: 0 0 0 2px white, 0 0 0 3px #5d6cf9;
                    background: #5d6cf9;
                }
            </style><script>
                if(typeof aysQuizOptions === 'undefined'){
                    var aysQuizOptions = [];
                }
                aysQuizOptions['1']  = 'eyJxdWl6X3ZlcnNpb24iOiI2LjUuMS4yIiwiY29yZV92ZXJzaW9uIjoiNi40LjIiLCJwaHBfdmVyc2lvbiI6IjguMi4xNCIsImNvbG9yIjoiIzVkNmNmOSIsImJnX2NvbG9yIjoiI2ZmZiIsInRleHRfY29sb3IiOiIjMDAwMDAwIiwiaGVpZ2h0Ijo0NTAsIndpZHRoIjowLCJlbmFibGVfbG9nZ2VkX3VzZXJzIjoib2ZmIiwiaW5mb3JtYXRpb25fZm9ybSI6ImRpc2FibGUiLCJmb3JtX25hbWUiOiJvbiIsImZvcm1fZW1haWwiOiJvbiIsImZvcm1fcGhvbmUiOiJvbiIsImltYWdlX3dpZHRoIjoiIiwiaW1hZ2VfaGVpZ2h0IjoiIiwiZW5hYmxlX2NvcnJlY3Rpb24iOiJvbiIsImVuYWJsZV9wcm9ncmVzc19iYXIiOiJvZmYiLCJlbmFibGVfcXVlc3Rpb25zX3Jlc3VsdCI6Im9uIiwicmFuZG9taXplX3F1ZXN0aW9ucyI6Im9mZiIsInJhbmRvbWl6ZV9hbnN3ZXJzIjoib24iLCJlbmFibGVfcXVlc3Rpb25zX2NvdW50ZXIiOiJvbiIsImVuYWJsZV9yZXN0cmljdGlvbl9wYXNzIjoib2ZmIiwicmVzdHJpY3Rpb25fcGFzc19tZXNzYWdlIjoiIiwidXNlcl9yb2xlIjpbXSwiY3VzdG9tX2NzcyI6IiIsImxpbWl0X3VzZXJzIjoib2ZmIiwibGltaXRhdGlvbl9tZXNzYWdlIjoiIiwicmVkaXJlY3RfdXJsIjoiIiwicmVkaXJlY3Rpb25fZGVsYXkiOjAsImFuc3dlcnNfdmlldyI6ImdyaWQiLCJlbmFibGVfcnRsX2RpcmVjdGlvbiI6Im9mZiIsImVuYWJsZV9sb2dnZWRfdXNlcnNfbWVzc2FnZSI6IiIsInF1ZXN0aW9uc19jb3VudCI6IiIsImVuYWJsZV9xdWVzdGlvbl9iYW5rIjoib2ZmIiwiZW5hYmxlX2xpdmVfcHJvZ3Jlc3NfYmFyIjoib2ZmIiwiZW5hYmxlX3BlcmNlbnRfdmlldyI6Im9mZiIsImVuYWJsZV9hdmVyYWdlX3N0YXRpc3RpY2FsIjoib2ZmIiwiZW5hYmxlX25leHRfYnV0dG9uIjoib24iLCJlbmFibGVfcHJldmlvdXNfYnV0dG9uIjoib2ZmIiwiZW5hYmxlX2Fycm93cyI6Im9mZiIsInRpbWVyX3RleHQiOiIiLCJxdWl6X3RoZW1lIjoiY2xhc3NpY19saWdodCIsImVuYWJsZV9zb2NpYWxfYnV0dG9ucyI6Im9mZiIsInJlc3VsdF90ZXh0IjoiWW91IHNjb3JlZCAlJXNjb3JlJSUuIFx1MDBhMFRoYXQgdG9vayB5b3UgJSV1c2VyX3Bhc3NfdGltZSUlLlxyXG5cclxuVGhlIGF2ZXJhZ2Ugc2NvcmUgaXMgJSVhdmdfc2NvcmUlJS4iLCJlbmFibGVfcGFzc19jb3VudCI6Im9mZiIsImhpZGVfc2NvcmUiOiJvbiIsInJhdGVfZm9ybV90aXRsZSI6IlBsZWFzZSByYXRlIHRoaXMgcXVpeiIsImJveF9zaGFkb3dfY29sb3IiOiIjNTY1NjU2IiwicXVpel9ib3JkZXJfcmFkaXVzIjoiOCIsInF1aXpfYmdfaW1hZ2UiOiIiLCJxdWl6X2JvcmRlcl93aWR0aCI6IjEiLCJxdWl6X2JvcmRlcl9zdHlsZSI6InNvbGlkIiwicXVpel9ib3JkZXJfY29sb3IiOiIjMDAwIiwicXVpel9sb2FkZXIiOiJkZWZhdWx0IiwiY3JlYXRlX2RhdGUiOm51bGwsImF1dGhvciI6IntcImlkXCI6XCIxXCIsXCJuYW1lXCI6XCJXYWRlIFRyZWdhc2tpc1wifSIsInF1ZXN0X2FuaW1hdGlvbiI6ImZhZGUiLCJmb3JtX3RpdGxlIjoiIiwiZW5hYmxlX2JnX211c2ljIjoib2ZmIiwicXVpel9iZ19tdXNpYyI6IiIsImFuc3dlcnNfZm9udF9zaXplIjoxNiwic2hvd19jcmVhdGVfZGF0ZSI6Im9mZiIsInNob3dfYXV0aG9yIjoib2ZmIiwiZW5hYmxlX2Vhcmx5X2ZpbmlzaCI6Im9mZiIsImFuc3dlcnNfcndfdGV4dHMiOiJvbl9ib3RoIiwiZGlzYWJsZV9zdG9yZV9kYXRhIjoib2ZmIiwiZW5hYmxlX2JhY2tncm91bmRfZ3JhZGllbnQiOiJvZmYiLCJiYWNrZ3JvdW5kX2dyYWRpZW50X2NvbG9yXzEiOiIjMDAwIiwiYmFja2dyb3VuZF9ncmFkaWVudF9jb2xvcl8yIjoiI2ZmZiIsInF1aXpfZ3JhZGllbnRfZGlyZWN0aW9uIjoidmVydGljYWwiLCJyZWRpcmVjdF9hZnRlcl9zdWJtaXQiOiJvZmYiLCJzdWJtaXRfcmVkaXJlY3RfdXJsIjoiIiwic3VibWl0X3JlZGlyZWN0X2RlbGF5IjoiMCIsInByb2dyZXNzX2Jhcl9zdHlsZSI6InNlY29uZCIsImVuYWJsZV9leGl0X2J1dHRvbiI6Im9mZiIsImV4aXRfcmVkaXJlY3RfdXJsIjoiIiwiaW1hZ2Vfc2l6aW5nIjoiY292ZXIiLCJxdWl6X2JnX2ltYWdlX3Bvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImN1c3RvbV9jbGFzcyI6IiIsImVuYWJsZV9zb2NpYWxfbGlua3MiOiJvZmYiLCJzb2NpYWxfbGlua3MiOnsibGlua2VkaW5fbGluayI6IiIsImZhY2Vib29rX2xpbmsiOiIiLCJ0d2l0dGVyX2xpbmsiOiIiLCJ2a29udGFrdGVfbGluayI6IiIsImluc3RhZ3JhbV9saW5rIjoiIiwieW91dHViZV9saW5rIjoiIiwiYmVoYW5jZV9saW5rIjoiIn0sInNob3dfcXVpel90aXRsZSI6Im9uIiwic2hvd19xdWl6X2Rlc2MiOiJvbiIsInNob3dfbG9naW5fZm9ybSI6Im9mZiIsIm1vYmlsZV9tYXhfd2lkdGgiOiIiLCJsaW1pdF91c2Vyc19ieSI6ImlwIiwiYWN0aXZlX2RhdGVfY2hlY2siOiJvZmYiLCJhY3RpdmVJbnRlcnZhbCI6IjIwMjQtMDEtMDQgMTk6MzI6MTQiLCJkZWFjdGl2ZUludGVydmFsIjoiMjAyNC0wMS0wNCAxOTozMjoxNCIsImFjdGl2ZV9kYXRlX3ByZV9zdGFydF9tZXNzYWdlIjoiVGhlIHF1aXogd2lsbCBiZSBhdmFpbGFibGUgc29vbiEiLCJhY3RpdmVfZGF0ZV9tZXNzYWdlIjoiVGhpcyBxdWl6IGhhcyBleHBpcmVkISIsImV4cGxhbmF0aW9uX3RpbWUiOiIwIiwiZW5hYmxlX2NsZWFyX2Fuc3dlciI6Im9mZiIsInNob3dfY2F0ZWdvcnkiOiJvZmYiLCJzaG93X3F1ZXN0aW9uX2NhdGVnb3J5Ijoib2ZmIiwiZGlzcGxheV9zY29yZSI6ImJ5X3BlcmNhbnRhZ2UiLCJlbmFibGVfcndfYXNud2Vyc19zb3VuZHMiOiJvbiIsImFuc19yaWdodF93cm9uZ19pY29uIjoiZGVmYXVsdCIsInF1aXpfYmdfaW1nX2luX2ZpbmlzaF9wYWdlIjoib2ZmIiwiZmluaXNoX2FmdGVyX3dyb25nX2Fuc3dlciI6Im9mZiIsImFmdGVyX3RpbWVyX3RleHQiOiIiLCJlbmFibGVfZW50ZXJfa2V5Ijoib24iLCJidXR0b25zX3RleHRfY29sb3IiOiIjZmZmZmZmIiwiYnV0dG9uc19wb3NpdGlvbiI6ImNlbnRlciIsInNob3dfcXVlc3Rpb25zX2V4cGxhbmF0aW9uIjoib25fYm90aCIsImVuYWJsZV9hdWRpb19hdXRvcGxheSI6Im9mZiIsImJ1dHRvbnNfc2l6ZSI6ImxhcmdlIiwiYnV0dG9uc19mb250X3NpemUiOiIyMCIsImJ1dHRvbnNfd2lkdGgiOiIiLCJidXR0b25zX2xlZnRfcmlnaHRfcGFkZGluZyI6IjMwIiwiYnV0dG9uc190b3BfYm90dG9tX3BhZGRpbmciOiIxMyIsImJ1dHRvbnNfYm9yZGVyX3JhZGl1cyI6IjgiLCJlbmFibGVfbGVhdmVfcGFnZSI6Im9uIiwiZW5hYmxlX3RhY2tlcnNfY291bnQiOiJvZmYiLCJ0YWNrZXJzX2NvdW50IjoiIiwicGFzc19zY29yZSI6ODAsInBhc3Nfc2NvcmVfbWVzc2FnZSI6IjxoMiBzdHlsZT1cInRleHQtYWxpZ246IGNlbnRlclwiPk1hYyBwb3dlciB1c2VyITxcL2gyPiIsImZhaWxfc2NvcmVfbWVzc2FnZSI6IjxoMiBzdHlsZT1cInRleHQtYWxpZ246IGNlbnRlclwiPllvdSBoYXZlIG11Y2ggdG8gbGVhcm4sIE1hYyBwYWRhd2FuLjxcL2gyPiIsInF1ZXN0aW9uX2ZvbnRfc2l6ZSI6MTYsInF1aXpfd2lkdGhfYnlfcGVyY2VudGFnZV9weCI6InBpeGVscyIsInF1ZXN0aW9uc19oaW50X2ljb25fb3JfdGV4dCI6ImRlZmF1bHQiLCJxdWVzdGlvbnNfaGludF92YWx1ZSI6IiIsImVuYWJsZV9lYXJseV9maW5zaF9jb21maXJtX2JveCI6Im9uIiwiZW5hYmxlX3F1ZXN0aW9uc19vcmRlcmluZ19ieV9jYXQiOiJvZmYiLCJzaG93X3NjaGVkdWxlX3RpbWVyIjoib2ZmIiwic2hvd190aW1lcl90eXBlIjoiY291bnRkb3duIiwicXVpel9sb2FkZXJfdGV4dF92YWx1ZSI6IiIsImhpZGVfY29ycmVjdF9hbnN3ZXJzIjoib2ZmIiwic2hvd19pbmZvcm1hdGlvbl9mb3JtIjoib2ZmIiwicXVpel9sb2FkZXJfY3VzdG9tX2dpZiI6IiIsImRpc2FibGVfaG92ZXJfZWZmZWN0Ijoib2ZmIiwicXVpel9sb2FkZXJfY3VzdG9tX2dpZl93aWR0aCI6MTAwLCJwcm9ncmVzc19saXZlX2Jhcl9zdHlsZSI6InRoaXJkIiwicXVpel90aXRsZV90cmFuc2Zvcm1hdGlvbiI6Im5vbmUiLCJzaG93X2Fuc3dlcnNfbnVtYmVyaW5nIjoibm9uZSIsInF1aXpfaW1hZ2Vfd2lkdGhfYnlfcGVyY2VudGFnZV9weCI6InBpeGVscyIsInF1aXpfaW1hZ2VfaGVpZ2h0IjoiIiwicXVpel9iZ19pbWdfb25fc3RhcnRfcGFnZSI6Im9mZiIsInF1aXpfYm94X3NoYWRvd194X29mZnNldCI6MCwicXVpel9ib3hfc2hhZG93X3lfb2Zmc2V0IjowLCJxdWl6X2JveF9zaGFkb3dfel9vZmZzZXQiOjUwLCJxdWl6X3F1ZXN0aW9uX3RleHRfYWxpZ25tZW50IjoiY2VudGVyIiwicXVpel9hcnJvd190eXBlIjoiZGVmYXVsdCIsInF1aXpfc2hvd193cm9uZ19hbnN3ZXJzX2ZpcnN0Ijoib24iLCJxdWl6X2Rpc3BsYXlfYWxsX3F1ZXN0aW9ucyI6Im9mZiIsInF1aXpfdGltZXJfcmVkX3dhcm5pbmciOiJvZmYiLCJxdWl6X3NjaGVkdWxlX3RpbWV6b25lIjoiQW1lcmljYVwvTG9zX0FuZ2VsZXMiLCJxdWVzdGlvbnNfaGludF9idXR0b25fdmFsdWUiOiIiLCJxdWl6X3RhY2tlcnNfbWVzc2FnZSI6IlRoaXMgcXVpeiBpcyBleHBpcmVkISIsInF1aXpfZW5hYmxlX2xpbmtlZGluX3NoYXJlX2J1dHRvbiI6Im9uIiwicXVpel9lbmFibGVfZmFjZWJvb2tfc2hhcmVfYnV0dG9uIjoib24iLCJxdWl6X2VuYWJsZV90d2l0dGVyX3NoYXJlX2J1dHRvbiI6Im9uIiwicXVpel9tYWtlX3Jlc3BvbnNlc19hbm9ueW1vdXMiOiJvZmYiLCJxdWl6X21ha2VfYWxsX3Jldmlld19saW5rIjoib2ZmIiwic2hvd19xdWVzdGlvbnNfbnVtYmVyaW5nIjoibm9uZSIsInF1aXpfbWVzc2FnZV9iZWZvcmVfdGltZXIiOiIiLCJlbmFibGVfcGFzc3dvcmQiOiJvZmYiLCJwYXNzd29yZF9xdWl6IjoiIiwicXVpel9wYXNzd29yZF9tZXNzYWdlIjoiIiwiZW5hYmxlX3NlZV9yZXN1bHRfY29uZmlybV9ib3giOiJvZmYiLCJkaXNwbGF5X2ZpZWxkc19sYWJlbHMiOiJvZmYiLCJlbmFibGVfZnVsbF9zY3JlZW5fbW9kZSI6Im9uIiwicXVpel9lbmFibGVfcGFzc3dvcmRfdmlzaWJpbGl0eSI6Im9mZiIsInF1ZXN0aW9uX21vYmlsZV9mb250X3NpemUiOjE2LCJhbnN3ZXJzX21vYmlsZV9mb250X3NpemUiOjE1LCJzb2NpYWxfYnV0dG9uc19oZWFkaW5nIjoiIiwicXVpel9lbmFibGVfdmtvbnRha3RlX3NoYXJlX2J1dHRvbiI6Im9uIiwiYW5zd2Vyc19ib3JkZXIiOiJvbiIsImFuc3dlcnNfYm9yZGVyX3dpZHRoIjoxLCJhbnN3ZXJzX2JvcmRlcl9zdHlsZSI6InNvbGlkIiwiYW5zd2Vyc19ib3JkZXJfY29sb3IiOiIjZGRkZGRkIiwic29jaWFsX2xpbmtzX2hlYWRpbmciOiIiLCJxdWl6X2VuYWJsZV9xdWVzdGlvbl9jYXRlZ29yeV9kZXNjcmlwdGlvbiI6Im9mZiIsImFuc3dlcnNfbWFyZ2luIjoxMiwicXVpel9tZXNzYWdlX2JlZm9yZV9yZWRpcmVjdF90aW1lciI6IiIsImJ1dHRvbnNfbW9iaWxlX2ZvbnRfc2l6ZSI6MjAsImFuc3dlcnNfYm94X3NoYWRvdyI6Im9mZiIsImFuc3dlcnNfYm94X3NoYWRvd19jb2xvciI6IiMwMDAiLCJxdWl6X2Fuc3dlcl9ib3hfc2hhZG93X3hfb2Zmc2V0IjowLCJxdWl6X2Fuc3dlcl9ib3hfc2hhZG93X3lfb2Zmc2V0IjowLCJxdWl6X2Fuc3dlcl9ib3hfc2hhZG93X3pfb2Zmc2V0IjoxMCwicXVpel9jcmVhdGVfYXV0aG9yIjoxLCJxdWl6X2VuYWJsZV90aXRsZV90ZXh0X3NoYWRvdyI6Im9uIiwicXVpel90aXRsZV90ZXh0X3NoYWRvd19jb2xvciI6IiNjZWNlY2UiLCJxdWl6X3RpdGxlX3RleHRfc2hhZG93X3hfb2Zmc2V0IjoyLCJxdWl6X3RpdGxlX3RleHRfc2hhZG93X3lfb2Zmc2V0IjoyLCJxdWl6X3RpdGxlX3RleHRfc2hhZG93X3pfb2Zmc2V0IjozLCJxdWl6X3Nob3dfb25seV93cm9uZ19hbnN3ZXJzIjoib2ZmIiwicXVpel90aXRsZV9mb250X3NpemUiOjI4LCJxdWl6X3RpdGxlX21vYmlsZV9mb250X3NpemUiOjIwLCJxdWl6X3Bhc3N3b3JkX3dpZHRoIjoiIiwicXVpel9yZXZpZXdfcGxhY2Vob2xkZXJfdGV4dCI6IiIsInF1aXpfbWFrZV9yZXZpZXdfcmVxdWlyZWQiOiJvZmYiLCJxdWl6X2VuYWJsZV9yZXN1bHRzX3RvZ2dsZSI6Im9mZiIsInF1aXpfcmV2aWV3X3RoYW5rX3lvdV9tZXNzYWdlIjoiIiwicXVpel9yZXZpZXdfZW5hYmxlX2NvbW1lbnRfZmllbGQiOiJvbiIsInF1ZXN0X2V4cGxhbmF0aW9uX2ZvbnRfc2l6ZSI6MTYsInF1ZXN0X2V4cGxhbmF0aW9uX21vYmlsZV9mb250X3NpemUiOjE2LCJxdWl6X3dhaXRpbmdfdGltZSI6Im9mZiIsIndyb25nX2Fuc3dlcnNfZm9udF9zaXplIjoxNiwid3JvbmdfYW5zd2Vyc19tb2JpbGVfZm9udF9zaXplIjoxNiwicXVpel9lbmFibGVfcXVlc3Rpb25faW1hZ2Vfem9vbSI6Im9mZiIsInJpZ2h0X2Fuc3dlcnNfZm9udF9zaXplIjoxNiwicmlnaHRfYW5zd2Vyc19tb2JpbGVfZm9udF9zaXplIjoxNiwicXVpel9kaXNwbGF5X21lc3NhZ2VzX2JlZm9yZV9idXR0b25zIjoib24iLCJxdWl6X2VuYWJsZV91c2VyX2NcdTA1NzBvb3NpbmdfYW5vbnltb3VzX2Fzc2Vzc21lbnQiOiJvZmYiLCJub3RlX3RleHRfZm9udF9zaXplIjoxNCwibm90ZV90ZXh0X21vYmlsZV9mb250X3NpemUiOjE0LCJxdWl6X3F1ZXN0aW9uc19udW1iZXJpbmdfYnlfY2F0ZWdvcnkiOiJvZmYiLCJxdWl6X2VuYWJsZV9jdXN0b21fdGV4dHNfZm9yX2J1dHRvbnMiOiJvbiIsInF1aXpfY3VzdG9tX3RleHRzX3N0YXJ0X2J1dHRvbiI6IlN0YXJ0IiwicXVpel9jdXN0b21fdGV4dHNfbmV4dF9idXR0b24iOiJOZXh0IiwicXVpel9jdXN0b21fdGV4dHNfcHJldl9idXR0b24iOiJQcmV2IiwicXVpel9jdXN0b21fdGV4dHNfY2xlYXJfYnV0dG9uIjoiQ2xlYXIiLCJxdWl6X2N1c3RvbV90ZXh0c19maW5pc2hfYnV0dG9uIjoiRmluaXNoIiwicXVpel9jdXN0b21fdGV4dHNfc2VlX3Jlc3VsdHNfYnV0dG9uIjoiTmV4dCIsInF1aXpfY3VzdG9tX3RleHRzX3Jlc3RhcnRfcXVpel9idXR0b24iOiJSZXN0YXJ0IiwicXVpel9jdXN0b21fdGV4dHNfc2VuZF9mZWVkYmFja19idXR0b24iOiJTZW5kIGZlZWRiYWNrIiwicXVpel9jdXN0b21fdGV4dHNfbG9hZF9tb3JlX2J1dHRvbiI6IkxvYWQgbW9yZSIsInF1aXpfY3VzdG9tX3RleHRzX2V4aXRfYnV0dG9uIjoiRXhpdCIsInF1aXpfY3VzdG9tX3RleHRzX2NoZWNrX2J1dHRvbiI6IkNoZWNrIiwicXVpel9jdXN0b21fdGV4dHNfbG9naW5fYnV0dG9uIjoiTG9nIEluIiwicXVpel9lbmFibGVfcXVpel9jYXRlZ29yeV9kZXNjcmlwdGlvbiI6Im9mZiIsInJlcXVpcmVkX2ZpZWxkcyI6bnVsbCwiZW5hYmxlX3RpbWVyIjoib2ZmIiwiZW5hYmxlX3F1aXpfcmF0ZSI6Im9mZiIsImVuYWJsZV9yYXRlX2F2ZyI6Im9mZiIsImVuYWJsZV9ib3hfc2hhZG93Ijoib24iLCJlbmFibGVfYm9yZGVyIjoib2ZmIiwicXVpel90aW1lcl9pbl90aXRsZSI6Im9mZiIsImVuYWJsZV9yYXRlX2NvbW1lbnRzIjoib24iLCJlbmFibGVfcmVzdGFydF9idXR0b24iOiJvZmYiLCJhdXRvZmlsbF91c2VyX2RhdGEiOiJvZmYiLCJ0aW1lciI6MTAwLCJxdWl6X2JnX2ltZ19kdXJpbmdfdGhlX3F1aXoiOiJvZmYiLCJzaG93X2Fuc3dlcnNfY2FwdGlvbiI6Im9uIiwicXVpel9lbmFibGVfa2V5Ym9hcmRfbmF2aWdhdGlvbiI6Im9uIiwic2hvd19xdWl6X2ltYWdlIjoib24iLCJxdWl6X2VuYWJsZV93aGF0c2FwcF9zaGFyZV9idXR0b24iOiJvZmYiLCJzdWJtaXRfcmVkaXJlY3RfYWZ0ZXIiOiIiLCJyd19hbnN3ZXJzX3NvdW5kcyI6dHJ1ZSwiaWQiOiIxIiwidGl0bGUiOiJtYWNPUyBrZXlib2FyZCBzaG9ydGN1dHMgcXVpeiIsImRlc2NyaXB0aW9uIjoiPHNwYW4gc3R5bGU9XCJmb250LXNpemU6IDcycHhcIj5cdTIzMDNcdTIzMjVcdTIxZTdcdTIzMThcdWQ4M2VcdWRkMTQ8XC9zcGFuPiIsInF1aXpfaW1hZ2UiOiIiLCJxdWl6X2NhdGVnb3J5X2lkIjoiMSIsInF1ZXN0aW9uX2lkcyI6IjQsNywxMCw5LDExLDMsNiwxMywzMSwyNiwyOCwxNSw4LDEyLDUsMTYsMjcsMjUsMjEsMzAsMiwxLDE0LDIyLDI5LDE5LDE4LDI0LDIwLDE3LDIzLDMyIiwib3JkZXJpbmciOiIxIiwicHVibGlzaGVkIjoiMSIsImludGVydmFscyI6bnVsbCwicXVpel91cmwiOm51bGwsImN1c3RvbV9wb3N0X2lkIjpudWxsLCJhdXRob3JfaWQiOiIwIiwicXVpel9hbmltYXRpb25fdG9wIjoxMDAsInF1aXpfZW5hYmxlX2FuaW1hdGlvbl90b3AiOiJvbiJ9';
        </script>
                    <input type='hidden' name='quiz_id' value='1'/>
                    <input type='hidden' name='start_date' class='ays-start-date'/>
                </form></div>
                            </div>
                            


<p class="has-text-align-center">If you encounter any answers you believe are incorrect, please comment below.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-well-do-you-know-your-macos-keyboard-shortcuts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/macOS-keyboard-shortcuts-quiz.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7356</post-id>	</item>
		<item>
		<title>Secret Sonoma design lead: Barbie</title>
		<link>https://wadetregaskis.com/secret-sonoma-design-lead-barbie/</link>
					<comments>https://wadetregaskis.com/secret-sonoma-design-lead-barbie/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 12 Dec 2023 21:13:50 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[CGContextHighlight2xScaledImages]]></category>
		<category><![CDATA[macOS Sonoma]]></category>
		<category><![CDATA[pink]]></category>
		<category><![CDATA[Retina]]></category>
		<category><![CDATA[Snafu]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=6579</guid>

					<description><![CDATA[macOS Sonoma is in so many ways a dumpster fire. It&#8217;s the worst Apple OS update I can remember (although admittedly there&#8217;s been some real stinkers over the years, so maybe I&#8217;m overlooking some repressed memories). There was of course the bug whereby encrypted external drives no longer mounted automatically. That was pretty special, because&#8230; <a class="read-more-link" href="https://wadetregaskis.com/secret-sonoma-design-lead-barbie/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>macOS Sonoma is in <em>so</em> many ways a dumpster fire.  It&#8217;s the worst Apple OS update I can remember (although admittedly there&#8217;s been some real stinkers over the years, so maybe I&#8217;m overlooking some repressed memories).</p>



<p>There was of course the bug whereby encrypted external drives no longer mounted automatically.  That was pretty special, because it reveals that Apple doesn&#8217;t test encrypted external drives anywhere in their QA process &#8211; possibly not even ad-hoc internally, since it&#8217;s hard to image that an Apple employee stumbling on this bug by accident would not bother to report it.  Or maybe they did but even Apple&#8217;s own employees&#8217; bug reports are ignored.  Consistent, at least.</p>



<p>Since virtually <em>every</em> external drive should be encrypted, this suggests Apple either believes nobody actually uses external drives, or that they don&#8217;t care.  At least that bug was mostly fixed in 14.1, although it took Apple a full month to get around to it.</p>



<h2 class="wp-block-heading">Radical pink redesign</h2>



<p>But perhaps the most <em>glaring</em> bug, in a very literal sense, was the rampant pinkification of the GUI.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="960" height="324" src="https://wadetregaskis.com/wp-content/uploads/2023/12/Safari-website-thumbnails.webp" alt="Screenshot of Safari's website thumbnails view showing inconsistent pink tinting of some thumbnails" class="wp-image-6580" style="width:480px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/Safari-website-thumbnails.webp 960w, https://wadetregaskis.com/wp-content/uploads/2023/12/Safari-website-thumbnails-256x86.webp 256w, https://wadetregaskis.com/wp-content/uploads/2023/12/Safari-website-thumbnails-512x173.webp 512w" sizes="auto, (max-width: 960px) 100vw, 960px" /><figcaption class="wp-element-caption">Safari website thumbnails</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="406" height="164" src="https://wadetregaskis.com/wp-content/uploads/2023/12/Random-UI-elements-in-web-pages-and-native-apps.png" alt="Small screenshot of a section of the Amazon website toolbar showing pink tinting of the gear &quot;settings&quot; icon" class="wp-image-6581" style="width:203px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/Random-UI-elements-in-web-pages-and-native-apps.png 406w, https://wadetregaskis.com/wp-content/uploads/2023/12/Random-UI-elements-in-web-pages-and-native-apps-256x103.png 256w" sizes="auto, (max-width: 406px) 100vw, 406px" /><figcaption class="wp-element-caption">Random GUI controls in web pages</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1700" height="1104" src="https://wadetregaskis.com/wp-content/uploads/2023/12/DPReview-article-hero-images.webp" alt="Screenshot of a section of the DPReview homepage showing pink tinting of an article hero image" class="wp-image-6582" style="width:850px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/DPReview-article-hero-images.webp 1700w, https://wadetregaskis.com/wp-content/uploads/2023/12/DPReview-article-hero-images-256x166.webp 256w, https://wadetregaskis.com/wp-content/uploads/2023/12/DPReview-article-hero-images-512x332@2x.webp 1024w, https://wadetregaskis.com/wp-content/uploads/2023/12/DPReview-article-hero-images-512x332.webp 512w" sizes="auto, (max-width: 1700px) 100vw, 1700px" /><figcaption class="wp-element-caption">…not to mention many actual images on web pages.</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="282" height="242" src="https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-1.webp" alt="" class="wp-image-6607" style="object-fit:cover" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-1.webp 282w, https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-1-256x220.webp 256w, https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-1@2x.webp 564w" sizes="auto, (max-width: 282px) 100vw, 282px" /><figcaption class="wp-element-caption">Even Apple&#8217;s own apps &#8211; and most poetically of all Quartz Debug &#8211; are afflicted.</figcaption></figure>
</div>


<p>As you can see, Sonoma brought with it a penchant for tinting things a gross light pink colour, including the rectangular extents of images that are otherwise invisible because they&#8217;re transparent.</p>



<h2 class="wp-block-heading">WTF?!</h2>


<div class="wp-block-image">
<figure class="alignright size-full is-resized"><img loading="lazy" decoding="async" width="528" height="1016" src="https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-settings-window.webp" alt="Screenshot of Quartz Debug's settings window" class="wp-image-6587" style="width:264px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-settings-window.webp 528w, https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-settings-window-133x256.webp 133w, https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-settings-window-266x512.webp 266w" sizes="auto, (max-width: 528px) 100vw, 528px" /></figure>
</div>


<p>I spent a <em>lot</em> of time debugging this.  Over days, weeks, and months.  Trying out new avenues as they came to me, even repeating some web &amp; forum searches over and over again in frustrated desperation.</p>



<p>I only just today figured out what&#8217;s going on.  84 days later.</p>



<p>One of my first instincts &#8211; right on day 0 &#8211; was that it was some Quartz Debug thing, because it looks a lot like some of the tinting options Quartz Debug has for things like flashing screen updates or showing opaque regions.  But the Quartz Debug app insisted everything was disabled.  I enabled everything anyway, and then disabled it all again, in case that would jog something free.  No bueno.</p>



<p>I then spent the better part of three months trying every superstition, every rumour or snippet of hearsay online, that might in any way related to this problem or conceivably fix it.  Nothing helped.</p>



<h2 class="wp-block-heading">Epiphany</h2>



<p>It was last night, when I was fiddling with some images in a gallery here on my website, that I discovered that adjusting the CSS dimensions of an image by just <em>a single pixel</em> could pinkify them.  Most importantly, restoring the dimensions to the <em>actual image dimensions</em> would fix them.  I&#8217;d already observed something similar to that, in various apps and websites, but it <em>felt</em> different to be directly triggering the problem myself.</p>



<p>That kept me up half the night, tossing and turning in bed, highly suspicious that this was a pivotal clue, but unsure what it pointed to.</p>



<p>Eventually, it led me right back to my very first instinct &#8211; Quartz Debug &#8211; and the tentative conclusion that this <em>was</em> in fact a Quartz Debug problem even if Quartz Debug claimed otherwise.  I become suspicious that the app was either not showing me the full range of settings, or just plain wrong about them.</p>



<p>And that was the insight I needed &#8211; right on the very first page of search results for &#8220;quartz debug defaults&#8221; was a link to <a href="https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Testing/Testing.html" data-wpel-link="external" target="_blank" rel="external noopener">Testing and Troubleshooting High-Resolution Content</a>.  Buried behind a disclosure triangle near the bottom is the magic string:</p>



<p class="has-text-align-center has-large-font-size" style="font-style:normal;font-weight:500"><code>CGContextHighlight2xScaledImages</code></p>



<p>That was set to YES, on my system.</p>



<p>Deleting it fixes the issue once affected apps are restarted (something Apple&#8217;s own documentation fails to mention &#8211; <em>kind of an important step, Apple!</em>).</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>defaults delete -g CGContextHighlight2xScaledImages</code></p>
<cite>The magic incantation to exorcise Barbie</cite></blockquote>



<p>In theory you can also disable this from the Quartz Debug app itself, it&#8217;s just hidden &#8211; in the Tools menu, <em>not</em> the actual settings window, is an item labelled &#8220;Color 1x Artwork&#8221;.  Note how the setting applies even when Quartz Debugging is disabled.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="688" height="596" src="https://wadetregaskis.com/wp-content/uploads/2023/12/Screenshot-of-the-Quartz-Debug-Tools-menu.webp" alt="" class="wp-image-6588" style="width:344px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/12/Screenshot-of-the-Quartz-Debug-Tools-menu.webp 688w, https://wadetregaskis.com/wp-content/uploads/2023/12/Screenshot-of-the-Quartz-Debug-Tools-menu-256x222.webp 256w, https://wadetregaskis.com/wp-content/uploads/2023/12/Screenshot-of-the-Quartz-Debug-Tools-menu-512x444.webp 512w" sizes="auto, (max-width: 688px) 100vw, 688px" /></figure>
</div>


<h2 class="wp-block-heading">So, wait, how did this come about?</h2>



<p>I have no idea why this only cropped up when Sonoma was installed.  Either Sonoma turned this on (unlikely…?) or it was already on yet wasn’t having any effect in prior OS releases (from 2017 onwards, at least, since my iMac Pro never exhibited this issue before Sonoma).</p>



<p>I know for sure that this setting did work at least going back over a decade, as I remember using this &#8211; on a different computer &#8211; for doing then-new-fangled Retina development… but I’m pretty sure I’ve never set this on my iMac Pro since it way post-dates the introduction of Retina.  Conceivably it was pulled across by Apple&#8217;s Migration Assistant.  Though, again, why did no earlier version of the OS, going back over a decade, exhibit this behaviour if it was supposedly enabled the whole time?</p>



<p>I know it&#8217;s not just me that&#8217;s been afflicted by this to date, as I found a sporadic few reports of this going back years, e.g. <a href="https://apple.stackexchange.com/questions/155602/highly-abnormal-graphic-glitch-on-yosemite-retina-all-white-backgrounds-appea" data-wpel-link="external" target="_blank" rel="external noopener">Highly abnormal graphic glitch on yosemite / retina: all white backgrounds appear pink</a>.  It pisses me off immensely that this page has <em>exactly</em> the keywords I was searching for right from the start &#8211; right in its title! &#8211; yet neither Bing nor Google can find it.</p>



<h2 class="wp-block-heading">Apple&#8217;s response?  &#8220;Fuck you&#8221; in mime.</h2>



<p>I actually filed a pretty detailed bug report with Apple about this, the day I discovered it (September 19th, 2023).  FB13190068.</p>



<p>Of course, they never responded in the slightest.</p>



<p>I provided a <em>bunch</em> of example images, even a screen recording to show how the pink comes and goes in certain circumstances.</p>



<p>I studiously tested every new macOS update and updated the bug report to note that they each had not fixed it.</p>



<p>The morale of this is, as always, that filing bug reports with Apple is an infuriating waste of time.  Mea culpa.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/secret-sonoma-design-lead-barbie/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2023/12/Quartz-Debug-1.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">6579</post-id>	</item>
		<item>
		<title>Blog like it&#8217;s 2005</title>
		<link>https://wadetregaskis.com/blog-like-its-2005/</link>
					<comments>https://wadetregaskis.com/blog-like-its-2005/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 29 Nov 2023 03:19:58 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[AUC]]></category>
		<category><![CDATA[Meta]]></category>
		<category><![CDATA[.mac]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Apple Backup]]></category>
		<category><![CDATA[Apple HomePage]]></category>
		<category><![CDATA[Dropbox]]></category>
		<category><![CDATA[Gmail]]></category>
		<category><![CDATA[Hotmail]]></category>
		<category><![CDATA[iCloud]]></category>
		<category><![CDATA[iCloud Drive]]></category>
		<category><![CDATA[iDisk]]></category>
		<category><![CDATA[iTools]]></category>
		<category><![CDATA[MobileMe]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=5926</guid>

					<description><![CDATA[On a bit of a whim &#8211; a silly tangent off of migrating to a new web host &#8211; I resurrected the &#8216;frozen in time&#8217; copy of my iTools / .Mac / MobileMe HomePage website. You can find it here, for better or worse. My old website dates back to at the latest September 2005&#8230; <a class="read-more-link" href="https://wadetregaskis.com/blog-like-its-2005/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>On a bit of a whim &#8211; a silly tangent off of <a href="https://wadetregaskis.com/migrated-hosts-out-of-the-subdomain/" data-wpel-link="internal">migrating to a new web host</a> &#8211; I resurrected the &#8216;frozen in time&#8217; copy of my <a href="https://apple.fandom.com/wiki/HomePage" data-wpel-link="external" target="_blank" rel="external noopener">iTools / .Mac / MobileMe HomePage</a> website.  You can find it <a href="https://wadetregaskis.com/MobileMe/Sites/index.html" data-wpel-link="internal">here</a>, for better or worse.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>iTools</summary><div class="wp-block-image">
<figure class="alignright size-full is-resized"><img loading="lazy" decoding="async" width="700" height="677" src="https://wadetregaskis.com/wp-content/uploads/2023/11/Apple-iTools.avif" alt="" class="wp-image-5928" style="width:350px;height:auto" srcset="https://wadetregaskis.com/wp-content/uploads/2023/11/Apple-iTools.avif 700w, https://wadetregaskis.com/wp-content/uploads/2023/11/Apple-iTools-256x248.avif 256w, https://wadetregaskis.com/wp-content/uploads/2023/11/Apple-iTools-512x495.avif 512w" sizes="auto, (max-width: 700px) 100vw, 700px" /></figure>
</div>


<p>Long before iCloud, <a href="https://en.wikipedia.org/wiki/MobileMe#iTools" data-wpel-link="external" target="_blank" rel="external noopener">iTools</a> was was of Apple&#8217;s first forays into web services as we recognise them today.  The headlining features were the ability to create an email address (free!) under the @mac.com domain (which I still use to this day!) and an online file storage and sharing system called <a href="https://en.wikipedia.org/wiki/IDisk" data-wpel-link="external" target="_blank" rel="external noopener">iDisk</a>, which was the foundation for several other services &amp; tools within the bundle including HomePage and <a href="https://en.wikipedia.org/wiki/MobileMe#Backup" data-wpel-link="external" target="_blank" rel="external noopener">Backup</a>.</p>



<p>Backup wasn&#8217;t exclusively tied to iDisk, and I used it (in the later .Mac era) for a couple of years to backup my essential documents to iDisk and my entire computer to a external hard drives and CDs.  I think it was my first <em>real</em> backup system…?  And I still have all those backups!</p>


<div class="wp-block-image">
<figure class="alignleft size-full is-resized"><img loading="lazy" decoding="async" width="819" height="879" src="https://wadetregaskis.com/wp-content/uploads/2023/11/iTools.avif" alt="" class="wp-image-5930" style="width:410px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/11/iTools.avif 819w, https://wadetregaskis.com/wp-content/uploads/2023/11/iTools-239x256.avif 239w, https://wadetregaskis.com/wp-content/uploads/2023/11/iTools-477x512.avif 477w, https://wadetregaskis.com/wp-content/uploads/2023/11/iTools-239x256@2x.avif 478w" sizes="auto, (max-width: 819px) 100vw, 819px" /></figure>
</div>


<p>I don&#8217;t recall that I used iTools, or at least not in any serious way.  I was still in high school when it came out (just), going into university, and I recall using my university email address during that time.</p>



<p>If I remember correctly, iTools was pretty well received by actual users but panned critically, with the press and technorati unimpressed by it in light of antecedents like <a href="https://en.wikipedia.org/wiki/Outlook.com" data-wpel-link="external" target="_blank" rel="external noopener">Hotmail</a> (launched three years earlier in 1997) and numerous existing &#8220;DIY&#8221; website builders and free webhosts (<a href="https://en.wikipedia.org/wiki/GeoCities" data-wpel-link="external" target="_blank" rel="external noopener">GeoCities</a>!).</p>



<p>There was also some resentment due to its Mac-only nature, although technically most of the services worked through a web browser on any platform, you just couldn&#8217;t create an account except on a Mac.</p>



<p>Remember, too, that Mac OS X didn&#8217;t exist when iTools launched.  It was Mac OS 9 &#8211; retroactively named &#8220;Classic Mac OS&#8221; re. <a href="https://en.wikipedia.org/wiki/Rosetta_(software)" data-wpel-link="external" target="_blank" rel="external noopener">Rosetta</a> etc &#8211; and for Mac users the world was a <em>very</em> different place.  Once Mac OS X did launch, iTools became even better with integration into the Finder (for iDisk).  This was over <em>six years</em> before <a href="https://en.wikipedia.org/wiki/Dropbox" data-wpel-link="external" target="_blank" rel="external noopener">Dropbox</a> even existed.</p>



<p>Hotmail and lesser known contemporaries pioneered free email, but with tight limits on usage (e.g. only 2 MB of email storage, enough for about two modern spam emails!).  .Mac offered 50 MB (upgradable to 2 GB for a fee).  This was three years before <a href="https://en.wikipedia.org/wiki/Gmail" data-wpel-link="external" target="_blank" rel="external noopener">Gmail</a> was launched, too (at which time the word &#8220;unlimited&#8221; started to get thrown around, although technically Gmail only offered 1 GB initially).</p>
</details>
</div></div>



<p>My old website dates back to <em>at the latest </em>September 2005 when I launched my blog, although I&#8217;m pretty sure I&#8217;d been using it for quite some time before that (though the <a href="https://web.archive.org" data-wpel-link="external" target="_blank" rel="external noopener">Wayback Machine</a> didn&#8217;t notice it for <em>three</em> more years, <a href="https://web.archive.org/web/20080920125519/http://homepage.mac.com/wadetregaskis/Menu14.html" data-wpel-link="external" target="_blank" rel="external noopener">until September 2008</a> 😂).</p>



<p>It took a little bit of effort to get it functioning again &#8211; it contained a lot of references to 3rd party resources (Google Adsense &amp; Analytics, Haloscan, etc) that are no longer valid / necessary, as well as to some more intrinsic .Mac HomePage functionality that&#8217;s long gone, like <a href="https://en.wikipedia.org/wiki/IDisk" data-wpel-link="external" target="_blank" rel="external noopener">iDisk</a> (the early file-sharing functionality a la Dropbox / iCloud Drive).  I pruned out most of that for the sake of removing long-dead, broken links &amp; images.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>.Mac</summary><div class="wp-block-image">
<figure class="alignleft size-full is-resized"><img loading="lazy" decoding="async" width="474" height="468" src="https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-retail-box-1.avif" alt="" class="wp-image-5932" style="width:237px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-retail-box-1.avif 474w, https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-retail-box-1-256x253.avif 256w" sizes="auto, (max-width: 474px) 100vw, 474px" /></figure>
</div>

<div class="wp-block-image">
<figure class="alignright size-full is-resized"><img loading="lazy" decoding="async" width="240" height="332" src="https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-logo.avif" alt="" class="wp-image-5929" style="width:120px" srcset="https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-logo.avif 240w, https://wadetregaskis.com/wp-content/uploads/2023/11/Mac-logo-185x256.avif 185w" sizes="auto, (max-width: 240px) 100vw, 240px" /></figure>
</div>


<p>.Mac was <a href="https://www.apple.com/newsroom/2002/07/17Apple-Launches-Mac/" data-wpel-link="external" target="_blank" rel="external noopener">a relaunch</a> of iTools &#8211; a major version update in technical terms, but also a major departure in that it was no longer free for all Mac users.  You now had to buy it!  For actual money!  And <em>keep</em> paying for it, every year, like a caveman!  $100!</p>



<p>I still remember receiving an actual box for .Mac, and I suspect that was when I first really used the service.  I think I might have received it through <a href="https://en.wikipedia.org/wiki/Apple_University_Consortium" data-wpel-link="external" target="_blank" rel="external noopener">the AUC</a> as part of their scholarship program which effectively launched my career, lining me up to work at Apple, move to California, and all the rest.</p>



<p>As for .Mac, it was somewhat ill-received, and despite <a href="https://www.apple.com/newsroom/2002/09/17Apples-Mac-Subscribers-Top-100-000/" data-wpel-link="external" target="_blank" rel="external noopener">Apple bragging about receiving 100,000 sign-ups</a>, the general attitude was that it was too little for too much.  It didn&#8217;t help that it then went years without much improvement or attention from Apple.</p>



<p>The name was a bit weird, too &#8211; clearly playing on the .com TLD, but in an era long before the expansion of TLDs, back when there was basically just .com, .net, .org, and .edu<sup data-fn="31698b84-b059-478b-a95e-9d77210cd3ca" class="fn"><a href="#31698b84-b059-478b-a95e-9d77210cd3ca" id="31698b84-b059-478b-a95e-9d77210cd3ca-link">1</a></sup>.  It&#8217;s interesting that to this day there&#8217;s <em>still</em> no .mac TLD (although there is .apple, albeit for no apparent reason since <a href="https://www.apple.com/legal/intellectual-property/tld/" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s retained by Apple for their exclusive use</a>, and they don&#8217;t use it).</p>



<p>Years later, in 2008, it was rebranded and &#8220;relaunched&#8221; yet again as MobileMe.  I for one don&#8217;t recall ever having significant issues with MobileMe, but it was widely lambasted for being unreliable.  I certainly don&#8217;t think it was any worse than iCloud (<em>especially</em> in light of macOS Sonoma&#8217;s catastrophic embuggering of iCloud Drive).</p>
</details>
</div></div>



<p>Incidentally, as part of this trip down memory lane, I also went through and fixed the broken images on my very earliest blog posts, that didn&#8217;t originally import into WordPress when <a href="https://wadetregaskis.com/converting-my-old-journal/" data-wpel-link="internal">I migrated off of MobileMe HomePage in 2012</a>, when the product was shut down by Apple (in favour of iCloud, which was mostly a PR move to distance themselves from perceived reliability problems with MobileMe &#8211; notably not an <em>actual</em> improvement in reliability, which to this day remains sorely needed).</p>


<ol class="wp-block-footnotes"><li id="31698b84-b059-478b-a95e-9d77210cd3ca">Well, plus .gov and .mil, but regular people never encountered those in everyday life. <a href="#31698b84-b059-478b-a95e-9d77210cd3ca-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/blog-like-its-2005/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2023/11/My-.Mac-website.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">5926</post-id>	</item>
		<item>
		<title>Apple&#8217;s timing problem</title>
		<link>https://wadetregaskis.com/apples-timing-problem/</link>
					<comments>https://wadetregaskis.com/apples-timing-problem/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 19 Nov 2023 06:34:48 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[M1 Ultra]]></category>
		<category><![CDATA[M2 Ultra]]></category>
		<category><![CDATA[M3 Ultra]]></category>
		<category><![CDATA[Sad]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=5586</guid>

					<description><![CDATA[The M1 was announced in November 2020. The M1 Ultra wasn&#8217;t announced for another sixteen months, in March 2022. It was clearly late. Very late. The M2 was presumed right around the corner (and it was, released just four months later). Why would anyone buy an M1 Ultra, using a two year old CPU architecture&#8230; <a class="read-more-link" href="https://wadetregaskis.com/apples-timing-problem/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>The M1 was announced in November 2020.  The M1 Ultra wasn&#8217;t announced for another <em>sixteen months</em>, in March 2022.  It was clearly late.  <em>Very</em> late.  The M2 was presumed right around the corner (and it was, released just four months later).  Why would anyone buy an M1 Ultra, using a <em>two year old</em> CPU architecture designed for <em>phones</em>, when its successor was due any day (and would surely be a big leap again in performance)?</p>



<p>So I didn&#8217;t buy an M1 Ultra.</p>



<p>The M2 was announced in July 2022.  The M2 Ultra wasn&#8217;t announced for another <em>eleven</em> months, in June 2023, <em>and</em> offered only a small performance gain over its predecessor.  There were rumours that the M3 was right around the corner (and it was, released just four months later).  Worse, the M3 <em>Pro</em> and <em>Max</em> were released simultaneous to the base M3, and the M3 Max actually outperforms the M2 Ultra in many workloads.</p>



<p>So I didn&#8217;t buy an M2 Ultra.</p>



<p>So now the question is:  when will the M3 Ultra come out?  If it takes eleven to sixteen months, like its predecessors, then we&#8217;ll be expecting the M4 by then, and this sad cycle will have repeated yet again.</p>



<p>Will I buy an M3 Ultra?</p>



<p>I hope so, but it&#8217;s up to Apple.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/apples-timing-problem/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5586</post-id>	</item>
		<item>
		<title>Reverting to an older version of Safari Technology Preview</title>
		<link>https://wadetregaskis.com/reverting-to-an-older-version-of-safari-technology-preview/</link>
					<comments>https://wadetregaskis.com/reverting-to-an-older-version-of-safari-technology-preview/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 18 Jun 2021 18:20:55 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[crashtastic]]></category>
		<category><![CDATA[Internet Archive]]></category>
		<category><![CDATA[Safari Technology Preview]]></category>
		<category><![CDATA[The Wayback Machine]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=4665</guid>

					<description><![CDATA[Apple try to make it impossible to revert to a prior version of Safari Technology Preview (STP) &#8211; and they also try to force updates to the latest version immediately, without user consent. This is bafflingly hostile behaviour for what is supposed to be a beta version of the browser that users voluntarily, out of&#8230; <a class="read-more-link" href="https://wadetregaskis.com/reverting-to-an-older-version-of-safari-technology-preview/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Apple try to make it impossible to revert to a prior version of Safari Technology Preview (STP) &#8211; and they also try to force updates to the latest version immediately, without user consent.  This is bafflingly hostile behaviour for what is supposed to be a beta version of the browser that users <em>voluntarily</em>, out of <em>charity</em>, help Apple debug.</p>



<p>It&#8217;s also highly problematic when new versions are flat-out broken.  Starting with around STP 124 I started experiencing consistent crashes on some websites, making them completely unusable in STP.  For the time-being I chose to use them separately in regular Safari, on the assumption that these egregious issues would be quickly fixed in the next STP version.  Well, three versions later and those bugs have not been fixed.  Not even close.</p>



<p>Now, with STP 126, it crashes on launch.  Every time.</p>



<p>Well, thankfully there <em>are</em> places where you can obtain the prior versions, even if Apple won&#8217;t provide them.  My preference is <a href="https://web.archive.org/" data-wpel-link="external" target="_blank" rel="external noopener">The Wayback Machine</a> &#8211; you can start with <a href="https://web.archive.org/web/*/https://developer.apple.com/safari/download/" data-wpel-link="external" target="_blank" rel="external noopener">this calendar</a>, from which you can pick a date and (with any luck) the download page for that date will point to the version you want.  You then download the disc image, delete the current copy of STP from your Applications folder (otherwise the pkg installer for the older version will refuse to work), and re-install the older version.</p>



<p>Once you&#8217;ve done that, make sure to turn Automatic Updates <em>off</em> in System Preferences, otherwise Apple will just trash your working version with the broken one again.</p>



<p>If you appreciate that &#8211; I certainly did; I like having a web browser that doesn&#8217;t crash on launch &#8211; remember that The Wayback Machine is run by the Internet Archive, a non-profit group, and they can always use <a href="https://archive.org/donate" data-wpel-link="external" target="_blank" rel="external noopener">monetary support</a> as well as <a href="https://archive.org/about/volunteerpositions.php" data-wpel-link="external" target="_blank" rel="external noopener">volunteers</a>.</p>



<p>P.S. Yes, I&#8217;m aware that their donation page is sadly a bit janky.  If you&#8217;re a web developer or designer, maybe you could volunteer some of your time to improve it? 😁</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/reverting-to-an-older-version-of-safari-technology-preview/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4665</post-id>	</item>
		<item>
		<title>People vs Products</title>
		<link>https://wadetregaskis.com/people-vs-products/</link>
					<comments>https://wadetregaskis.com/people-vs-products/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 28 Aug 2020 23:04:28 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Ideas]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[leadership]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[management]]></category>
		<category><![CDATA[people manager]]></category>
		<category><![CDATA[Peter Principle]]></category>
		<category><![CDATA[technical lead]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=4580</guid>

					<description><![CDATA[I&#8217;ve experienced an interesting arc over my twenty or so years (thus far) of software development. I started out as a one-person shop, doing my own things, selling shareware. I had no manager nor technical lead. I had to make all my own decisions, in all aspects, without guidance or assistance. Subsequently, during my four&#8230; <a class="read-more-link" href="https://wadetregaskis.com/people-vs-products/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve experienced an interesting arc over my twenty or so years (thus far) of software development.</p>



<p>I started out as a one-person shop, doing my own things, selling shareware.  I had no manager nor technical lead.  I had to make all my own decisions, in all aspects, without guidance or assistance.</p>



<p>Subsequently, during my four years at Apple, I did have a manager, but they focused on people, not the technical &#8211; myself and/or my colleagues collectively made the technical decisions, and provided technical leadership, and effectively set the product direction.  My managers were there to make that as easy as possible for us.</p>



<p>Over my nearly eight years at Google, I observed the tail half of a major cultural transition for Google.  Long before I started, Google had explicitly laid down a culture where managers were not product / technical leads.  The two roles were physically separated, between different people, and they operated independently.  Managers focused on people &#8211; career growth, happiness, basic productivity, &amp; skills &#8211; while tech leads focused on the technical, the product.  In fact the manager role was so principled about focus on people that managers would sometimes help their direct reports <em>leave the company</em>, if that was simply what was best for those people for their own success &amp; growth.  And, to be clear, not in a &#8220;you aren&#8217;t working out&#8221; sense, but for engineers that were excellent and simply didn&#8217;t have deserved opportunities available to them at Google.</p>



<p>By the time I joined, that culture was half-gone, but still present enough in my division for me to experience it.  But by the time I left the culture was heavily weighted towards managers being technical leads.</p>



<p>In my nearly three years now at LinkedIn, I&#8217;ve completed that arc.  LinkedIn culturally &amp; executively emphasises managers as technical / product leads even moreso than Google ever did.  As far as I&#8217;ve been told, LinkedIn always has (meaning, this is presumably the culture Yahoo had too, from which LinkedIn forked).</p>



<p>Having experienced most of this spectrum, I finally feel qualified to pass judgement on it.</p>



<figure class="wp-block-pullquote"><blockquote><p>Managers should not be leads.</p></blockquote></figure>



<p>I immediately, intuitively recognised &amp; appreciated this at Google, but now I&#8217;m certain of it.</p>



<p>People management &amp; (technical) product leadership are fundamentally at odds with each other.  The needs of individuals are often at odds with the needs of the product.  The product might need Natalie to really focus on churning through a bunch of menial tasks, but to evolve, Natalie might really need design experience &amp; leadership opportunities.</p>



<p>Having one person (in authority) try to wear both hats creates conflict, bias, and inefficiency.  It discourages dialogue, because you can never <em>really</em> trust where the polymorph stands.  The roles require different skillsets, which rarely coexist in a single person and in any case are difficult to keep up to date in parallel.  Context-switching between them is burdensome.  It creates a power imbalance and perverse incentives.</p>



<p>Even if an individual is exceptionally talented at mitigating those problems, they simply don&#8217;t have the time to do both well.  Being a product or technical lead is <em>at least</em> a full-time job.  Likewise, helping a team of any real size grow as individuals requires way more hands-on, one-on-one attention than most people realise.  It&#8217;s hard enough being good at either one of them alone &#8211; anyone that attempts doing both simultaneously ends up doing neither effectively.</p>



<p>I&#8217;ve had the opportunity to be both a technical lead <em>only</em> and a manager <em>only</em>.  This is quite rare in the tech industry.  I deeply appreciated being able to focus on <em>just one</em> of those roles at a time.  I could be consistent, deliberate, and <em>honest</em>.  I could, as a manager, tell people exactly what I thought they should or shouldn&#8217;t work on, irrespective of what the product(s) need, because I knew the technical lead(s) would worry about those angles.  Conversely, when I was a technical lead, I could lay out what was simply, objectively best for the project, uncomplicated by individuals&#8217; interests.  In either case, there was real, other human being that could be debated with, as necessary, to find happy mediums.</p>



<p>Yet beyond just being more efficient and effective, the serendipitous consequence was that it <em>gave agency to the individuals</em> &#8211; whenever a conflict arose between people and products, it was revealed to them, and the implicit decision about it at least in part theirs to make.  Most importantly, they knew that <em>whichever</em> way they leaned they had someone in their corner who had their back.</p>



<p>(Of course, sometimes they didn&#8217;t <em>like</em> having to make that decision, but putting it on them forced them to take control and responsibility for themselves, and evolve into more confident, happy, motivated developers.)</p>



<p>I suppose it&#8217;s no surprise that companies tends this way &#8211; to conflate people with products.  These days, for many big tech companies, people literally <em>are</em> the products, and their humanity inevitably stripped away in the process.  People are &#8220;promoted&#8221; into management from technical positions, and often by way of <a href="https://en.wikipedia.org/wiki/Peter_principle" data-wpel-link="external" target="_blank" rel="external noopener">the Peter Principle</a>, are not actually good people managers, <em>nor</em> able to relinquish their former role and ways of thinking.  A hierarchy of technical leads in manager&#8217;s clothing becomes self-sustaining, self-selecting, and self-enforcing.</p>



<p>The question is:  what&#8217;s the antidote?</p>



<p>Acknowledgement:  I was inspired to pen this post by reading <a href="https://www.linkedin.com/in/rtwortham/" data-wpel-link="external" target="_blank" rel="external noopener">Tanner Wortham</a>&#8216;s <a href="https://worth.am/manager-product-owner-fail/" data-wpel-link="external" target="_blank" rel="external noopener">Why Manager as Product Owner Will Usually Fail</a>, which is essentially positing the same thing albeit in different terminology.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/people-vs-products/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4580</post-id>	</item>
		<item>
		<title>Why I wanted to intern at Apple</title>
		<link>https://wadetregaskis.com/why-i-wanted-to-intern-at-apple/</link>
					<comments>https://wadetregaskis.com/why-i-wanted-to-intern-at-apple/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 08 Jul 2020 21:19:13 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[AUC]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Death Valley]]></category>
		<category><![CDATA[Grand Canyon]]></category>
		<category><![CDATA[Internship]]></category>
		<category><![CDATA[Statement of Motivation]]></category>
		<category><![CDATA[Yosemite]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=4555</guid>

					<description><![CDATA[I just found this while reviewing some very old backups. Like most things of this ancient era, I&#8217;d completely forgotten about it, so it&#8217;s been fascinating to look back &#8211; as if in the 3rd person &#8211; at my younger, far away self. I don&#8217;t recall why exactly, but evidently I had to write some&#8230; <a class="read-more-link" href="https://wadetregaskis.com/why-i-wanted-to-intern-at-apple/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I just found this while reviewing some very old backups.  Like most things of this ancient era, I&#8217;d completely forgotten about it, so it&#8217;s been fascinating to look back &#8211; as if in the 3rd person &#8211; at my younger, far away self.</p>



<p>I don&#8217;t recall why exactly, but evidently I had to write some kind of cover letter in order to intern at Apple.  I don&#8217;t even know if this was addressed to Apple, or was perhaps just part of the visa process.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Statement of Motivation</p><p>My internship offered at Apple Computer Inc. is a fantastic opportunity for me to be introduced to and get involved with one of the worlds leading and most innovative technology companies.  It will provide exposure to their current &#8211; and possibly future &#8211; hardware products, as well as the processes by which they develop them.</p><p>Additionally, the time in the U.S.A. will provide exposure to U.S. culture and general life, which will be an invaluable grounding should I pursue further work in the U.S.A. (at a later date).  While I have no immediate plans to do so, I would certainly like to have the option, as the U.S.A. is the worldwide hub for development of advanced computer technology – the best place for someone in my industry to end up.</p><p>In terms of furthering my studies and career, the impact is almost immeasurable.  My employability – not just in Australia, but also internationally – will be increased tremendously by the internship, both from the training and experience provided as well as from the impressive addition it would make to any resume.  Specifically, I hope it will open the door to future employment at Apple Computer Inc.</p><p>On a personal level, I’d like to see a bit of North America as a tourist, as much as I can – visit all the cliché spots, like the Grand Canyon, Death Valley, Yosemite, all those.  I think it’d be a great experience, and hopefully a lot of fun.</p><cite>21-year-old me, in 2005</cite></blockquote>



<p>I&#8217;ve been to all three of those places now.  Two out of the three were indeed fun, as hoped.</p>



<p>A fun footnote was that the internship paid $25 US / hour, @ 40 hours per week.  That was about $33 AUD / hour, given exchange rates at the time.  That compared <em>preeetty</em> favourably with my prior internships &#8211; $18.50 AUD / hour at NEC, $15.79 AUD / hour at PIRVic &#8211; and beat the pants off the first real job I recall having, at a flower nursery, in high school, for an amazing $5 AUD / hour.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/why-i-wanted-to-intern-at-apple/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4555</post-id>	</item>
	</channel>
</rss>
