Jekyll2024-01-12T19:32:21+00:00https://maddie.info//feed.xml@maddiefuzzTechnical writing and musing from the void. iOS 17 StoreKit Examples2023-12-22T00:55:00+00:002023-12-22T00:55:00+00:00https://maddie.info//appledev/2023/12/22/ios-17-storekit-examples<p>The new StoreKit views in iOS 17 make it a lot easier to add in-app purchases to your projects.
There’s a simple gotcha that I ran into, though, and I wanted to document it.</p>
<p>The two main StoreKit views that ship with iOS 17 are <code class="language-plaintext highlighter-rouge">StoreView</code> and <code class="language-plaintext highlighter-rouge">SubscriptionStoreView</code>.
Their initializers both ask for IDs, but have different expectations about what constitutes an ID.</p>
<h2 id="storeview">StoreView</h2>
<p><code class="language-plaintext highlighter-rouge">StoreView</code> is the view that you would use for consumable and non-consumable products. It expects
the IDs of the products in a Collection, and the IDs are whatever you typed in as the “Product ID”.</p>
<p>For example:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">productIDs</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="s">"Product1"</span><span class="p">,</span> <span class="s">"Product2"</span><span class="p">]</span>
<span class="kt">StoreView</span><span class="p">(</span><span class="nv">ids</span><span class="p">:</span> <span class="n">productIDs</span><span class="p">)</span></code></pre></figure>
<h2 id="subscriptionstoreview">SubscriptionStoreView</h2>
<p>A <code class="language-plaintext highlighter-rouge">SubscriptionStoreView</code> requires a group ID. You’ll give each of your subscription’s tiers a
product ID, but those aren’t used for querying the items. This group ID is autogenerated by
App Store Connect (or your StoreKit Test Configuration).</p>
<p>For example:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">groupID</span> <span class="o">=</span> <span class="s">"7E08E2CD"</span>
<span class="kt">SubscriptionStoreView</span><span class="p">(</span><span class="nv">groupID</span><span class="p">:</span> <span class="n">groupID</span><span class="p">)</span></code></pre></figure>
<h2 id="example-project">Example Project</h2>
<p>I’ve also put together an example project to show all of this working together. It doesn’t need any
configuration in App Store Connect, because I’m using a StoreKit Test Configuration.</p>
<p><a href="/assets/StoreKitExample.zip">StoreKit Example</a></p>The new StoreKit views in iOS 17 make it a lot easier to add in-app purchases to your projects. There’s a simple gotcha that I ran into, though, and I wanted to document it.Apple II Auto-Bootable Disks, Pt. 12023-10-07T16:28:42+00:002023-10-07T16:28:42+00:00https://maddie.info//2023/10/07/apple-ii-auto-bootable-disks-pt-1<p>As someone who didn’t grow up around 8-bit microcomputers, learning more about the Apple II has
been quite the learning process. There are a lot of books around, a ton of them are on the
<a href="https://archive.org/">internet archive</a>. I’ve included links to some good starting resources in the
references section at the bottom of the page.</p>
<p>There are a couple ways to create a floppy disk that auto-executes something when the
computer is first powered on. This article will cover most simple procedure for BASIC, and a
subsequent article will cover more advanced methods.</p>
<h1 id="apple-dos">Apple DOS</h1>
<p>To start with, we’re going to need to boot into <a href="https://en.wikipedia.org/wiki/Apple_DOS">Apple DOS</a>.
There are disk images that you can <a href="https://mirrors.apple2.org.za/ftp.apple.asimov.net/images/masters/">download</a>.
I’m using DOS version 3.3, which is the best-known and most-used version of Apple DOS.</p>
<p><img src="/assets/apple2/autoexec/1-boot-dos.png" alt="Applesoft DOS 3.3 System Master" /></p>
<blockquote>
<p>If you have a disk that’s been formatted for Apple DOS, it technically has a copy of DOS on that
diskette. The DOS is just the disk input/output routines and commands that we’ll be using.</p>
</blockquote>
<p>This emulator is equipped with two diskette drives; in these examples I’ll be booting from drive 1,
and creating the bootable media in drive 2. Your syntax will differ slightly if you only have one
diskette drive.</p>
<h1 id="booting-to-a-basic-program">Booting to a BASIC Program</h1>
<p>Once we’ve booted to DOS, we’ll create a new BASIC program.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>] NEW
] 10 HOME
] 20 PRINT "MADDIE'S DISK"
] 30 END
</code></pre></div></div>
<p>If we were to type <code class="language-plaintext highlighter-rouge">RUN</code> at this point, we’ll see the screen clear, <code class="language-plaintext highlighter-rouge">MADDIE'S DISK</code> print out
and then the BASIC program will exit.</p>
<p>We’ll insert a blank diskette into Drive 2 (virtually, in this case), and execute:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>] INIT HELLO,D2
</code></pre></div></div>
<p>This initializes the new diskette, creates a BASIC program called <code class="language-plaintext highlighter-rouge">HELLO</code> and sets up the information
on the disk so that it’s flagged to be automatically executed on boot. Running <code class="language-plaintext highlighter-rouge">CATALOG</code> shows
the new file <code class="language-plaintext highlighter-rouge">HELLO</code>. The <code class="language-plaintext highlighter-rouge">A</code> alongside it stands for <code class="language-plaintext highlighter-rouge">APPLESOFT BASIC</code>, the type of file that it is.</p>
<p><img src="/assets/apple2/autoexec/3-init-disk.png" alt="three" /></p>
<p>The program that you typed into memory is automatically saved to this file.</p>
<blockquote>
<p>The <code class="language-plaintext highlighter-rouge">,D2</code> portion of the command instructs DOS to execute the command on the second disk drive.
The system will remember the last drive used, and continue to use that drive until told otherwise.</p>
</blockquote>
<h2 id="testing-the-diskette">Testing the Diskette</h2>
<p>Now, if you swap your new diskette into Drive 1, and reboot the system, you should see
<code class="language-plaintext highlighter-rouge">MADDIE'S DISK</code> (or whatever you typed) at the top of the screen, followed by a <code class="language-plaintext highlighter-rouge">]</code> BASIC prompt.</p>
<p><img src="/assets/apple2/autoexec/4-autoexec-basic.png" alt="BASIC program runs on boot" /></p>
<p>You can store any kind of BASIC program you’d like in <code class="language-plaintext highlighter-rouge">HELLO</code>, and update it, using the <code class="language-plaintext highlighter-rouge">LOAD</code> and
<code class="language-plaintext highlighter-rouge">SAVE</code> commands. Pressing the <code class="language-plaintext highlighter-rouge">RESET</code> key will also drop you to a BASIC prompt.</p>
<p><code class="language-plaintext highlighter-rouge">INIT</code> has also installed a copy of Apple DOS to this diskette. You’ll be missing other files that
are on the Master Disk, but this floppy is now bootable and contains commands such as <code class="language-plaintext highlighter-rouge">INIT</code> and
<code class="language-plaintext highlighter-rouge">CATALOG</code> for disk operation.</p>
<h1 id="references">References</h1>
<p><a href="https://archive.org/details/applerefjan78/page/n1/mode/2up">Apple II Reference Manual</a></p>
<p><a href="https://archive.org/details/apple-ii-basic-programming">Apple II BASIC Programming Manual</a></p>
<p><a href="https://archive.org/details/manuals-apple">Apple Computer Manuals Collection</a></p>As someone who didn’t grow up around 8-bit microcomputers, learning more about the Apple II has been quite the learning process. There are a lot of books around, a ton of them are on the internet archive. I’ve included links to some good starting resources in the references section at the bottom of the page.Simple and Small Git Hosting2023-09-05T15:31:02+00:002023-09-05T15:31:02+00:00https://maddie.info//2023/09/05/simple-and-small-git-hosting<p>There are regular discussions on the fediverse about self-hosted git, and they generally cover
software that provides a similar experience to GitHub. Gitea and GitLab are the two names that I
see with the most frequency.</p>
<p>We personally have a family Gitea instance, and I like it a lot! It is heavy software, though,
and can cost a decent bit to keep going. I want to cover an alternative that will work out much
cheaper, with less administrative hassle. If you don’t need all of the additional features software
like Gitea provides (Issue Tracker, Kanban boards, Wiki, etc.), bare git repos may serve you very
well.</p>
<p>I’ll cover some different ground today, and this post should serve more as a choose-your-own-adventure
(with less grues, hopefully). There are many ways to use git, based on your needs, and your own
judgment should lead you to the solutions that will work for you.</p>
<h1 id="whats-a-bare-repo">What’s a Bare Repo?</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
└── full_repo
└── .git
├── branches
├── config
├── description
├── HEAD
├── hooks
├── info
├── objects
└── refs
</code></pre></div></div>
<p>In a normal git repository, all of your versioning information is stored in the <code class="language-plaintext highlighter-rouge">.git</code> folder
inside your project. Your working copy lives in the main folder (full_repo), where you do your work,
commit, switch branches, resolve merge conflicts, etc. If this isn’t familiar, I would recommend
starting with a different resource to learn the <a href="https://www.git-scm.com/">fundamentals of git</a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── bare_repo
│ ├── branches
│ ├── config
│ ├── description
│ ├── HEAD
│ ├── hooks
│ ├── info
│ ├── objects
│ └── refs
</code></pre></div></div>
<p>A bare repo moves everything in the <code class="language-plaintext highlighter-rouge">.git</code> folder up one directory. The entire folder <em>is the git
repository</em>. This means that you can’t have a working copy in this folder - the only things in here
are branches, tags, commits and other atomic git goodies!</p>
<blockquote>
<p>As an aside, you can also <code class="language-plaintext highlighter-rouge">git clone</code> and <code class="language-plaintext highlighter-rouge">git pull</code> from a non-bare repository, but you cannot
<code class="language-plaintext highlighter-rouge">git push</code>.</p>
</blockquote>
<p>Git will operate on remote repositories over different transport protocols. Gitea, GitLab and Github
support ssh and https transport. Git will also operate on ‘remote repositories’ on the same
filesystem! It really doesn’t care either way.</p>
<p>For example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git init --bare bare_repo
Initialized empty Git repository in /home/foobar/bare_repo/
$ git clone ./bare_repo cloned_repo
Cloning into 'cloned_repo'...
</code></pre></div></div>
<p>This will create a bare repo with no working copy, and a full clone with a working copy.
In the cloned version, there’s an <code class="language-plaintext highlighter-rouge">origin</code> remote preconfigured:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/cloned_repo$ git remote -v
origin /home/foobar/./bare_repo (fetch)
origin /home/foobar/./bare_repo (push)
</code></pre></div></div>
<p>This is just an example, but you may be happy to stop here. When you’re in <code class="language-plaintext highlighter-rouge">cloned_repo</code>, you can
<code class="language-plaintext highlighter-rouge">git push</code> your commits and they’ll just get pushed to the other folder. No remote server needed.</p>
<h1 id="ssh-its-time-for-transports">SSH! It’s time for Transports</h1>
<p><code class="language-plaintext highlighter-rouge">git clone</code> supports quite a few different transport protocols for operating on remote repositories.
It supports ssh, git, http(s), and (s)ftp. I’m going to focus on ssh and https today.</p>
<p>If you have ssh access to a server, you have SSH transport available. You can use an SCP-like syntax
to clone a repository. Assume that what we did above was running on a remote server, on your LAN
at <code class="language-plaintext highlighter-rouge">127.0.0.5</code>. You can clone it like so:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone foobar@127.0.0.5:~/bare_repo
</code></pre></div></div>
<p>This will behave exactly the same way as when we did it with a folder. The difference is, when you go
to <code class="language-plaintext highlighter-rouge">git push</code> or <code class="language-plaintext highlighter-rouge">git pull</code>, it will go over SSH transport, and you’ll be asked to authenticate.
This is the transport you’re already using if you’re familiar with Gitea or similar software. It’s a
feature of <code class="language-plaintext highlighter-rouge">git</code>, all on its own, no big web frontend needed. 😁</p>
<h1 id="https">HTTPS</h1>
<p>If you want to serve a read-only copy, just stick your repo in a folder that’s served by nginx or
your web server of choice. This is just an endpoint, like using SSH
transport above. You can <code class="language-plaintext highlighter-rouge">git clone</code> or <code class="language-plaintext highlighter-rouge">git pull</code> from the URL, but there is no web frontend.</p>
<p>You will need to regularly run
<a href="https://git-scm.com/docs/git-update-server-info">git update-server-info</a>
to (re)generate auxiliary information files so that <code class="language-plaintext highlighter-rouge">git</code> can properly discover what is hosted
on your HTTPS endpoint.</p>
<p>If you <em>do</em> want a web frontend, git comes with
<a href="https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb">GitWeb</a>.
I’m covering a lot of ground in this post, though, and don’t want to cover too many topics at once.
Hopefully the documentation I’ve linked is sufficient to help you set up GitWeb, if you’re so inclined.</p>
<h1 id="ip-addresses-are-annoying">IP Addresses are Annoying!</h1>
<p>IP Addresses are not easily human-parseable. They’re harder to remember than domain names. Having some
kind of DNS resolution on your local network is a huge quality of life improvement. I’ll provide two
solutions for DNS resolution, and you can pick which one you’d like based on your use case and comfort
with service configuration.</p>
<h2 id="avahi--zeroconf">Avahi / Zeroconf</h2>
<p><a href="https://en.wikipedia.org/wiki/Zero-configuration_networking">Zeroconf</a>
(Zero-Configuration Networking) is a set of technologies that allows for broadcasting DNS information
directly from the client (your server, in this case), using
<a href="https://en.wikipedia.org/wiki/Multicast_DNS">multicast DNS</a>.</p>
<p>The specific implementation that you’ll be using is <a href="https://www.avahi.org/">Avahi</a>. The setup process
should be similar on different distributions of linux, but I’ll cover setup on Debian and derivatives.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt-get install avahi-daemon
$ sudo systemctl start avahi-daemon
</code></pre></div></div>
<p>That’s all that you need for DNS resolution. Your server is now broadcasting its domain name.</p>
<p>Great, but what is it? It’s based on your hostname.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ hostname
baz
</code></pre></div></div>
<p>Avahi will broadcast on the multicast
<a href="https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains">TLD</a>
<code class="language-plaintext highlighter-rouge">local</code>. Your server should be available at <code class="language-plaintext highlighter-rouge">(hostname).local</code>. In this example you should be
able to connect to <code class="language-plaintext highlighter-rouge">baz.local</code>.</p>
<h2 id="local-dns-server">Local DNS Server</h2>
<p>If you don’t want to use multicast DNS, running your own domain name server is an option. I personally
use <a href="https://en.wikipedia.org/wiki/Unbound_(DNS_server)">unbound</a>.</p>
<p>Configuration of unbound is highly dependent on your network configuration and it would not be possible
for me to cover every permutation. Consider this the advanced method.</p>
<blockquote>
<p>Some software, such as <a href="https://pfsense.org">pfsense</a>, simplifies the operation of a DNS resolver
greatly. If you’re running pfsense, I would highly recommend taking a look at their documentation
about <a href="https://docs.netgate.com/pfsense/en/latest/services/dns/resolver.html">DNS resolver configuration</a>.
Similarly, if you’re running open-wrt, there is documentation to
<a href="https://openwrt.org/docs/guide-user/base-system/dhcp">configure a local DNS server</a>.</p>
</blockquote>
<h1 id="the-barest-of-bare-bones-git-hosting">The Barest of Bare-bones Git Hosting</h1>
<p>With DNS or zeroconf set up, all the pieces are in place. Revisiting the example from the transport
section, you can (for example) clone your repository now by running:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone foobar@baz.local:~/bare_repo
</code></pre></div></div>
<p>Your remote <code class="language-plaintext highlighter-rouge">origin</code> will be pre-populated, and you can interact with it from the command-line in
all of the regular ways.</p>
<blockquote>
<p>If your username is the same on both your local machine and server, you can omit <code class="language-plaintext highlighter-rouge">username@</code></p>
</blockquote>
<h1 id="summary">Summary</h1>
<p>There are a lot of reasons you might need something heavier, like Gitea, but there are a lot of
situations where you don’t. I use this setup in addition to our gitea instance, as not every
repository needs to be a full <em>project</em>, but I still want to stash an authoritative copy somewhere.</p>
<p>To recap, we’ve covered:</p>
<ul>
<li>Bare git repositories</li>
<li>Git remotes, both local and over the network</li>
<li>Transport protocols for git</li>
<li>Domain name resolution</li>
</ul>
<p>Running a bare SSH transport server like this is incredibly lightweight - I can get away with
only 2GB of RAM provisioned for my backup server. This can translate into real savings, especially
if you’re running a cloud VPS.</p>
<blockquote>
<p>If you need some more features than bare repositories can provide,
<a href="https://gitolite.com/gitolite/">Gitolite</a>
has been recommended to me. I haven’t personally used it, and can’t speak from firsthand
experience, but it may fit your needs nicely.</p>
</blockquote>
<p>I hope this was helpful! Always be n00bin. 👩💻</p>There are regular discussions on the fediverse about self-hosted git, and they generally cover software that provides a similar experience to GitHub. Gitea and GitLab are the two names that I see with the most frequency.RC2014: Z80 Assembly for Catgirls 😻2023-08-29T00:00:00+00:002023-08-29T00:00:00+00:00https://maddie.info//2023/08/29/rc2014-z80-assembly-for-catgirls<blockquote>
<p>“RC2014 is a simple 8 bit Z80 based modular computer originally built to run Microsoft BASIC.
It is inspired by the home built computers of the late 70s and computer revolution of the early 80s.
It is not a clone of anything specific, but there are suggestions of the ZX81, UK101, S100,
Superboard II and Apple I in here. It nominally has 8K ROM, 32K RAM, runs at 7.3728MHz and
communicates over serial at 115,200 baud.” - <a href="https://rc2014.co.uk">RC2014 Homepage</a></p>
</blockquote>
<p><img src="https://maddie.info//assets/images/posts/rc2014/pride2.png" alt="alt test" /></p>
<p>I soldered this kit together recently, which was an undertaking that I underestimated. The Pro kit
(pictured above) requires over 1,000 joints to be soldered just on the backplane itself.</p>
<p>My kit came with the compact flash and digital I/O modules. With the ROM jumpers set to the correct
page, the kit boots to the Z80 SBC Boot ROM, which can then boot the install of CP/M 2.2 that comes
preinstalled on the CF card.</p>
<h1 id="editor-woes">Editor Woes</h1>
<p>I might one day learn how <code class="language-plaintext highlighter-rouge">ED.COM</code> works. Today is not that day. Instead, I’ve installed <code class="language-plaintext highlighter-rouge">NVEDIT</code>
using <code class="language-plaintext highlighter-rouge">DOWNLOAD.COM</code>. There’s a copy of <code class="language-plaintext highlighter-rouge">DOWNLOAD.COM</code> preinstalled on the virtual <code class="language-plaintext highlighter-rouge">A:</code> drive.
Workflow will differ depending on your editor of choice, but writing assembly will be the same as
long as you’re using the standard CP/M tools for development. I may write a future post looking into
<a href="https://smallcomputercentral.com/small-computer-monitor/">Small Computer Monitor</a>, at which point
your assembly mnemonics and syntax will vary slightly.</p>
<h1 id="a-quick-aside">A Quick Aside</h1>
<p>CP/M ships with three executables, <code class="language-plaintext highlighter-rouge">ASM.COM</code>, <code class="language-plaintext highlighter-rouge">DDT.COM</code>, and <code class="language-plaintext highlighter-rouge">LOAD.COM</code>. Once you’ve written your
source code, you can assemble your program to hex with <code class="language-plaintext highlighter-rouge">ASM FILE.ASM</code>. The file extension is also
implicit if you’ve named your file <code class="language-plaintext highlighter-rouge">.ASM</code>. This outputs a <code class="language-plaintext highlighter-rouge">.HEX</code> file.</p>
<p>You can invoke the <code class="language-plaintext highlighter-rouge">DDT</code> debugger on it, and execute it. If you want to build a self-contained
executable, invoke <code class="language-plaintext highlighter-rouge">LOAD</code> on your <code class="language-plaintext highlighter-rouge">.HEX</code> file and it will generate a <code class="language-plaintext highlighter-rouge">.COM</code> file.</p>
<h1 id="there-are-eight-lights">There Are Eight Lights!</h1>
<p style="display: block; text-align: center;"><img src="https://maddie.info//assets/images/posts/rc2014/four_lights.gif" alt="Picard saying 'There are four lights!'" /></p>
<p>The <a href="https://rc2014.co.uk/modules/digital-io/">Digital I/O module</a> makes for a great first device to fiddle with. It is addressed as device 0,
accepts an 8-bit value that it displays in two’s complement with 8 LEDs, and has eight momentary
pushbuttons that are read back as an 8-bit value. The LEDs it comes with are very bright, though!
Let’s fix that and turn them off.</p>
<p>The official page for this module mentions this little blurb about controlling it:</p>
<blockquote>
<p>In BASIC the port can be read with the INP(0) command which will return a number from 0 to 255,
and written to with OUT 0,x where x is the number to output from 0 to 255</p>
<p>In assembly language, the mnemonics <code class="language-plaintext highlighter-rouge">in a,(0)</code> and out <code class="language-plaintext highlighter-rouge">(0),a</code> do the same function</p>
</blockquote>
<p>Some research and trial/error led me to this simple program that will turn those lights off. On the
Apple II, a popular memory location for the <code class="language-plaintext highlighter-rouge">ORG</code> statement (where this program is loaded into memory)
is <code class="language-plaintext highlighter-rouge">$8000</code>, but in CP/M <code class="language-plaintext highlighter-rouge">$0100</code> is a standard choice. Note that the <code class="language-plaintext highlighter-rouge">h</code> in <code class="language-plaintext highlighter-rouge">0100h</code> stands for
hexadecimal, and is just the convention that CP/M’s assembler uses.</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">org</span> <span class="mh">0100h</span>
<span class="nf">mvi</span> <span class="nv">a</span><span class="p">,</span><span class="mh">00h</span>
<span class="nf">out</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="nf">ret</span></code></pre></figure>
<p>So, what does the rest of this do? Well, if we want none of the lights lit up, we’d want to send <code class="language-plaintext highlighter-rouge">0</code>
to the device. CP/M’s assembler just uses the <code class="language-plaintext highlighter-rouge">A</code> register for OUT commands. So we need to stick <code class="language-plaintext highlighter-rouge">0</code>
into the <code class="language-plaintext highlighter-rouge">A</code> register.</p>
<p>We’ll do this with <code class="language-plaintext highlighter-rouge">MVI</code>, which is the <code class="language-plaintext highlighter-rouge">move immediate data</code> instruction. <code class="language-plaintext highlighter-rouge">00h</code>
is a hexadecimal constant for 0, and once it’s in the proper register, we just call <code class="language-plaintext highlighter-rouge">out (0)</code>. This
sends the data in the <code class="language-plaintext highlighter-rouge">A</code> register to the device on the bus of ID <code class="language-plaintext highlighter-rouge">0</code>.</p>
<p>That’s all of the “doing stuff” assembly. <code class="language-plaintext highlighter-rouge">RET</code> returns function to CP/M; Without <code class="language-plaintext highlighter-rouge">RET</code>, the lights
will extinguish, but the system will also hang.</p>
<p>So, altogether:</p>
<ul>
<li>Stick our assembled data in memory at <code class="language-plaintext highlighter-rouge">0100h</code></li>
<li>Load <code class="language-plaintext highlighter-rouge">00h</code> into the <code class="language-plaintext highlighter-rouge">A</code> register</li>
<li>Send the contents of the <code class="language-plaintext highlighter-rouge">A</code> register to device of ID <code class="language-plaintext highlighter-rouge">0</code></li>
<li>Return execution to parent (returning control to CP/M).</li>
</ul>
<p>Actually pretty simple!</p>
<p>Let’s do something more fun, though.</p>
<h1 id="reinventing-the-signal-lamp-poorly">Reinventing The Signal Lamp, Poorly</h1>
<p>CP/M has a <code class="language-plaintext highlighter-rouge">BIOS</code> and <code class="language-plaintext highlighter-rouge">BDOS</code> (Basic Disk Operating System), collectively referred to as the <code class="language-plaintext highlighter-rouge">FDOS</code>.
The documentation for this can be found in
<a href="http://www.gaby.de/cpm/manuals/archive/cpm22htm/ch5.htm">Chapter 5</a> of the CP/M 2 Manual. This
includes an <a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a> that can be called
from your program. For now, we’ll just mess with <code class="language-plaintext highlighter-rouge">1: Console Input</code>.</p>
<blockquote>
<p>In general, the function number is passed in register C with the information address in the
double byte pair DE. Single byte values are returned in register A …</p>
</blockquote>
<p>So we want function number <code class="language-plaintext highlighter-rouge">1</code>. Let’s use that same <code class="language-plaintext highlighter-rouge">MVI</code> instruction to stick <code class="language-plaintext highlighter-rouge">01h</code> in
register <code class="language-plaintext highlighter-rouge">C</code>, this time.</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">mvi</span> <span class="nv">c</span><span class="p">,</span><span class="mh">01h</span></code></pre></figure>
<p>Okay, so now we need to call the <code class="language-plaintext highlighter-rouge">FDOS</code> functions. The documentation states:</p>
<blockquote>
<p>As mentioned above, access to the FDOS functions is accomplished by passing a function number
and information address through the primary point at location BOOT+0005H</p>
</blockquote>
<blockquote>
<p>All standard CP/M versions assume BOOT=0000H, which is the base of random access memory.</p>
</blockquote>
<p><code class="language-plaintext highlighter-rouge">0000h</code> + <code class="language-plaintext highlighter-rouge">0005h</code> = <code class="language-plaintext highlighter-rouge">0005h</code> , So let’s CALL it!</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">call</span> <span class="mh">0005h</span></code></pre></figure>
<p>Remember, single byte values are returned in register A. That makes the last step the same as in
the first example. We’ll call:</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">out</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span></code></pre></figure>
<p>Combining everything together, our entire program is:</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">org</span> <span class="mh">0100h</span>
<span class="nf">mvi</span> <span class="nv">c</span><span class="p">,</span><span class="mh">01h</span>
<span class="nf">call</span> <span class="mh">0005h</span>
<span class="nf">out</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="nf">ret</span></code></pre></figure>
<p>Altogether:</p>
<ul>
<li>Stick our assembled data in memory at <code class="language-plaintext highlighter-rouge">0100h</code></li>
<li>Call the console input function, getting a single byte from console input</li>
<li>Send that to device (0)</li>
<li>Return execution to parent</li>
</ul>
<p>Now you can see the binary representation of whichever key you press!</p>
<p style="display: block; text-align: center;"><img src="https://maddie.info//assets/images/posts/rc2014/signal_lamp.gif" alt="Demonstration" /></p>
<p>But wait, yours doesn’t quite do that..?</p>
<p>When you run it, you’ll notice that your program executes after one keystroke. Not as
fun as it could be. If you take a closer look at
<a href="http://www.gaby.de/cpm/manuals/archive/cpm22html/ch5.html">Chapter 5</a>, you’ll find a solution to
this in just a few more lines of ASM. I am doing the annoying thing, and leaving it as an exercise
to the reader. 🤔</p>
<p>…</p>
<p>Alright, alright. The solution is below. 😁</p>
<style type="text/css">
.spoiler {
background-color: #000;
color: #000;
}
</style>
<div class="spoiler">
org 0100h<br />
mvi c,01h<br />
call 0005h<br />
out (0)<br />
cpi '*'<br />
jnz 0100h<br />
ret<br />
</div>
<p><br /></p>
<h1 id="resources-used">Resources Used</h1>
<p><a href="https://altairclone.com/downloads/manuals/8080%20Programmers%20Manual.pdf">8080 Programmers Manual</a></p>
<p><a href="http://www.gaby.de/cpm/manuals/archive/cpm22htm/">CP/M 2 Manual</a></p>“RC2014 is a simple 8 bit Z80 based modular computer originally built to run Microsoft BASIC. It is inspired by the home built computers of the late 70s and computer revolution of the early 80s. It is not a clone of anything specific, but there are suggestions of the ZX81, UK101, S100, Superboard II and Apple I in here. It nominally has 8K ROM, 32K RAM, runs at 7.3728MHz and communicates over serial at 115,200 baud.” - RC2014 HomepageEnder 3 EXP3 pinout for hardware hacking2023-07-01T00:00:00+00:002023-07-01T00:00:00+00:00https://maddie.info//hardware/2023/07/01/ender3-exp3-pinout<p>The Ender 3 display is based on the open Reprap screen design, but includes a header called <code class="language-plaintext highlighter-rouge">EXP3</code>
that allows for communication with a single cable. This header communicates with the display over
SPI, but the documentation I could find online was sparse and conflicting. I verified the pinout
and created this graphic to document this header’s pinout.</p>
<p>This display uses the ST7920 controller. The arduino library u8g2 has support for it.</p>
<p><img src="/assets/ender3/ender3-exp3-pinout.png" alt="Graphic explaining the pinout. Textual pinout below" /></p>
<h2 id="text-documentation-for-accessibility">Text Documentation for Accessibility</h2>
<p>The notch on the display board is on the top. Pins are left to right.</p>
<table style="display: table; margin-left: auto; margin-right: auto; width: 100%;">
<tbody>
<tr>
<td>Ground</td>
<td>Chip Select</td>
<td>Knob Rotation</td>
<td>Knob Rotation 2</td>
<td>Beeper</td>
</tr>
<tr>
<td>5V</td>
<td>Data</td>
<td>Clock</td>
<td>Unknown</td>
<td>Button</td>
</tr>
</tbody>
</table>
<h3 id="spi-pins">SPI Pins</h3>
<ul>
<li>Chip Select</li>
<li>Data</li>
<li>Clock</li>
</ul>
<h3 id="digital-buttons">Digital Buttons</h3>
<ul>
<li>Knob Rotation 1</li>
<li>Knob Rotation 2</li>
<li>Button</li>
</ul>The Ender 3 display is based on the open Reprap screen design, but includes a header called EXP3 that allows for communication with a single cable. This header communicates with the display over SPI, but the documentation I could find online was sparse and conflicting. I verified the pinout and created this graphic to document this header’s pinout.Interfacing with the Dreamcast Controller in KallistiOS2023-06-03T00:00:00+00:002023-06-03T00:00:00+00:00https://maddie.info//homebrew/dreamcast/2023/06/03/interfacing-with-the-dreamcast-controller-in-kos<p>I’ve been doing some Dreamcast homebrew this weekend, using the great
<a href="https://github.com/KallistiOS/KallistiOS/">KallistiOS</a> as well as
<a href="https://github.com/sizious/dcload-ip">dcload-ip</a>. Together they make
for a great development environment, and the <a href="https://en.wikipedia.org/wiki/Dreamcast">Dreamcast</a>
itself is the easiest console I’ve ever worked with to homebrew.</p>
<p>This is an example I put together to show how to poll the controller for
button presses, and use it to update the VMU. It’s really just what I’ve learned
so far about KOS.</p>
<h1 id="technical-stuff">Technical Stuff</h1>
<p>This example is a modified lcd.c from the KallistiOS <code class="language-plaintext highlighter-rouge">examples/</code> folder.
It uses the maple API to poll for that status, using <code class="language-plaintext highlighter-rouge">maple_dev_status()</code>,
which returns a struct. This is just added as part of the main loop logic.
The <code class="language-plaintext highlighter-rouge">status</code> struct’s button member is checked against the defined bit values
for the buttons we’re interested in.</p>
<p>The rest of the VMU code from lcd.c is used to animate a breakout-style
paddle that moves to the left and right. Start quits the program.</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
</pre></td><td class="code"><pre><span class="cp">#include <dc/vmu_fb.h>
#include <kos.h>
</span>
<span class="cp">#include <stdint.h>
</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">paddle</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
<span class="mi">0</span><span class="n">b11111111</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">static</span> <span class="n">vmufb_t</span> <span class="n">vmufb</span><span class="p">;</span>
<span class="n">KOS_INIT_FLAGS</span><span class="p">(</span><span class="n">INIT_DEFAULT</span> <span class="o">|</span> <span class="n">INIT_MALLOCSTATS</span><span class="p">);</span>
<span class="cm">/* Your program's main entry point */</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">;</span>
<span class="n">maple_device_t</span> <span class="o">*</span><span class="n">vmu</span><span class="p">;</span>
<span class="n">cont_btn_callback</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">CONT_START</span><span class="p">,</span>
<span class="p">(</span><span class="n">cont_btn_callback_t</span><span class="p">)</span><span class="n">arch_exit</span><span class="p">);</span>
<span class="c1">// Controller status</span>
<span class="n">maple_device_t</span> <span class="o">*</span><span class="n">device</span> <span class="o">=</span> <span class="n">maple_enum_dev</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
<span class="n">y</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>
<span class="c1">// Main Loop</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="o">==</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="n">cont_state_t</span> <span class="o">*</span><span class="n">status</span> <span class="o">=</span> <span class="n">maple_dev_status</span><span class="p">(</span><span class="n">device</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">status</span><span class="o">-></span><span class="n">buttons</span> <span class="o">&</span> <span class="n">CONT_DPAD_LEFT</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">x</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">x</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">status</span><span class="o">-></span><span class="n">buttons</span> <span class="o">&</span> <span class="n">CONT_DPAD_RIGHT</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">x</span> <span class="o"><</span> <span class="mi">40</span><span class="p">)</span> <span class="p">{</span>
<span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">vmufb_clear</span><span class="p">(</span><span class="o">&</span><span class="n">vmufb</span><span class="p">);</span>
<span class="n">vmufb_paint_area</span><span class="p">(</span><span class="o">&</span><span class="n">vmufb</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">paddle</span><span class="p">);</span>
<span class="n">vmu</span> <span class="o">=</span> <span class="n">maple_enum_type</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">MAPLE_FUNC_LCD</span><span class="p">);</span>
<span class="n">vmufb_present</span><span class="p">(</span><span class="o">&</span><span class="n">vmufb</span><span class="p">,</span> <span class="n">vmu</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>I’ve been doing some Dreamcast homebrew this weekend, using the great KallistiOS as well as dcload-ip. Together they make for a great development environment, and the Dreamcast itself is the easiest console I’ve ever worked with to homebrew.Indexed addressing modes on the MOS 65022023-03-19T00:00:00+00:002023-03-19T00:00:00+00:00https://maddie.info//2023/03/19/indexed-addressing-modes-on-the-MOS-6502<p>There are four indexed addressing modes on the MOS 6502. I’ve found the last one, <code class="language-plaintext highlighter-rouge">indirect indexed</code>, the most useful in my high-res graphics mode experiments on the Apple II+, but wanted to write about them all a bit. There are interactive examples at the end of this post (requires Javascript).</p>
<h1 id="absolute-indexed">Absolute Indexed</h1>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">STA</span> <span class="kc">$</span><span class="mi">0100</span><span class="p">,</span><span class="nv">Y</span></code></pre></figure>
<p>The absolute indexed addressing mode adds the contents of either the X or Y register to the memory address given. This can be used when you need to access or modify multiple memory addresses anywhere within the 16-bit address space. Note that the maximum index range is limited to 8 bits, the width of the 6502 registers.</p>
<p>Assuming the Y register contains <code class="language-plaintext highlighter-rouge">$08</code>, the above instruction will fetch the contents of <code class="language-plaintext highlighter-rouge">$0100+$08=$0108</code> and store it in the accumulator.</p>
<h1 id="zero-page-indexed">Zero-page Indexed</h1>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">STA</span> <span class="kc">$</span><span class="nv">F2</span><span class="p">,</span><span class="nv">X</span></code></pre></figure>
<p>The zero-page indexed addressing mode is very similar to the above, absolute indexed mode. The difference is that there is only one byte for the first argument, which results in an address that is always in the zero page. If the calculated address will fall outside the zero page, it wraps.</p>
<p>Assuming that the X register contains <code class="language-plaintext highlighter-rouge">$0F</code>, the calculated address will be <code class="language-plaintext highlighter-rouge">$F2+$0F = $0101</code>. This falls outside of the zero-page, which will wrap to address <code class="language-plaintext highlighter-rouge">$01</code>.</p>
<p>Note that you should normally use the X register for this addressing mode. You can use the Y register, but it only works on the X register mnemonics (<code class="language-plaintext highlighter-rouge">LDX</code>, <code class="language-plaintext highlighter-rouge">STX</code>).</p>
<h1 id="indexed-indirect">Indexed Indirect</h1>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">LDA</span> <span class="p">(</span><span class="kc">$</span><span class="mi">00</span><span class="p">,</span> <span class="nv">X</span><span class="p">)</span></code></pre></figure>
<p>The syntax for using the indexed indirect instruction is shown above. <code class="language-plaintext highlighter-rouge">$00</code> is an absolute address referring to memory address <code class="language-plaintext highlighter-rouge">0000</code>. X refers to the contents of the X register. Note that you cannot substitute X for Y while using indexed indirect. You likewise cannot use the X register with indirect indexed. Both this and the next addressing mode must be used with the accompanying register shown in these examples.</p>
<p>The contents of the X register will be added to the absolute address. Assuming X contains <code class="language-plaintext highlighter-rouge">$02</code>, the resulting address is <code class="language-plaintext highlighter-rouge">$00 + $02 = $02</code>. This is the memory location from which to fetch an address and retrieve the value stored in this address.</p>
<p>Note that this fetches a 16-bit address in little-endian format. For example, when reading the address at <code class="language-plaintext highlighter-rouge">$02</code>, it will use the <code class="language-plaintext highlighter-rouge">$02</code> value as the low-order byte and <code class="language-plaintext highlighter-rouge">$03</code> as the high-order byte.</p>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="err">0002:</span> <span class="err">20</span>
<span class="err">0003:</span> <span class="err">01</span> </code></pre></figure>
<h1 id="indirect-indexed">Indirect Indexed</h1>
<figure class="highlight"><pre><code class="language-nasm" data-lang="nasm"><span class="nf">STA</span> <span class="p">(</span><span class="kc">$</span><span class="mi">00</span><span class="p">),</span><span class="nv">Y</span></code></pre></figure>
<p>The last indexed addressing mode is confusingly called <code class="language-plaintext highlighter-rouge">indirect indexed</code>, but is different than the previous mode in the order of operations. In the previous addressing mode, the index was added before reading the address. In indirect indexed, the register is added after the indirect address is read. (Confusing? It’s hard to explain too. 😵💫)</p>
<p>For example, <code class="language-plaintext highlighter-rouge">indexed indirect</code> would allow you to select a 16-bit address out of a table, while <code class="language-plaintext highlighter-rouge">indirect indexed</code> would allow you to iterate through memory <em>after</em> the 16-bit address is fetched. Paying close attention to the parentheses may help clarify this.</p>
<p>When provided with the zero-page address <code class="language-plaintext highlighter-rouge">$00</code>, this address is used as the low-order byte. The next address, in this example <code class="language-plaintext highlighter-rouge">$01</code>, is used as the high order byte. The contents of the y register are then added to this 16 bit address, allowing for indexed reference anywhere in the 6502’s memory space.</p>
<p>The last interactive example uses this to loop through memory. Modifying the address in the zero-page would also allow you to iterate through more than 256 values.</p>
<h1 id="summary">Summary</h1>
<p>I found the <code class="language-plaintext highlighter-rouge">indirect indexed</code> mode the most useful when working with high-res graphics mode on the Apple II. A single page of HGR memory is <code class="language-plaintext highlighter-rouge">$2000</code> (or 8192!) bytes long. All of these modes are useful for different situations, though.</p>
<p>The below 6502 assembler/emulator has been adapted from the source of the <a href="https://github.com/skilldrick/6502js">6502js</a> project, by <a href="https://twitter.com/skilldrick">Nick Morgan</a>. The modified source is available <a href="/assets/6502js/assembler.js">here</a>.</p>
<div class="widget">
<div class="examples">
<h6>Examples:</h6>
<input type="button" value="Absolute Indexed" class="reinitializeButton" onclick="absoluteIndexed()" />
<input type="button" value="Zero Page Indexed" class="reinitializeButton" onclick="zeropageIndexed()" />
<input type="button" value="Indexed Indirect" class="reinitializeButton" onclick="indexedIndirect()" />
<input type="button" value="Indirect Indexed" class="reinitializeButton" onclick="indirectIndexed()" />
</div>
<div class="buttons">
<h6>Assembler:</h6>
<input type="button" value="Assemble" class="assembleButton" />
<input type="button" value="Run" class="runButton" />
<input type="button" value="Reset" class="resetButton" />
<input type="button" value="Hexdump" class="hexdumpButton" />
<input type="button" value="Disassemble" class="disassembleButton" />
<input type="button" value="Notes" class="notesButton" />
</div>
<textarea id="exampleTextArea" class="code"></textarea>
<canvas class="screen" width="160" height="160"></canvas>
<div class="debugger">
<input type="checkbox" class="debug inline-element" name="debug" />
<label class="inline-element" for="debug">Debugger</label>
<div class="minidebugger"></div>
<div class="buttons">
<input type="button" value="Step" class="stepButton" />
<input type="button" value="Jump to ..." class="gotoButton" />
</div>
</div>
<div class="monitorControls">
<label class="inline-element" for="monitoring">Monitor</label>
<input class="monitoring" id="monitorCheckbox" type="checkbox" name="monitoring" />
<label class="inline-element" for="start">Start: $</label>
<input class="start" type="text" value="0" name="start" />
<label class="inline-element" for="length">Length: $</label>
<input class="length" type="text" value="0130" name="length" />
</div>
<div class="monitor"><pre><code></code></pre></div>
<div class="messages"><pre><code></code></pre></div>
</div>
<script src="/assets/6502js/es5-shim.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="/assets/6502js/assembler.js"></script>
<script>
window.onload = function() {
absoluteIndexed();
document.getElementById("monitorCheckbox").click()
}
function absoluteIndexed() {
document.getElementById('exampleTextArea').value =
`; Store $08 in the Y register, and $BA in the accumulator
LDY #$08
LDA #$BA
; Use absolute indexed mode to store the accumulator
; in $0100+Y
STA $0100,Y
`
}
function zeropageIndexed() {
document.getElementById('exampleTextArea').value =
`; Store $0F in the Y register, and $BA in the accumulator
LDX #$0F
LDA #$BA
; Use absolute indexed mode to store the accumulator
; in $F2+$0F, which wraps to $01 in the zero-page
STA $F2,X
`
}
function indexedIndirect() {
document.getElementById('exampleTextArea').value =
`; Store data in $0120 to illustrate later retrieval
LDA #$50
STA $0120
; Store memory address $0120 in location $02 and $03.
; This is stored in "reverse" order, as $20, $01,
; because the 6502 is little-endian.
LDA #$20
STA $02
LDA #$01
STA $03
LDX #$02
; Indexed indirect memory addressing, which reads the
; memory address at $00 + $02 = $02, then reads the
; contents of that memory address ($02 and $03) and
; stores that value in the accumulator
LDA ($00, X)
`
}
function indirectIndexed() {
document.getElementById('exampleTextArea').value =
`; Store the low-order memory address #$10 at location
; $00
LDA #$10
STA $00
; Store the working data #$BA in $03, to be referenced
; later for copies 🐑
LDA #$BA
STA $03
; Verify the Y register is initialized to 0
LDY #$00
; Start of infinite loop
LOOP:
; Load the #$BA we stashed away earlier into
; the accumulator
LDA $03
; Reference 16-bit address in the zero page, at $00
; and $01 . Only the low-order byte is provided at
; $00 , the next byte ($01) is implicitly used
; as the high-order byte
STA ($00),Y
; Increment the accumulator and jump back up
; to start the loop again
INY
JMP LOOP
`
}
</script>
<link href="/assets/6502js/style.css" rel="stylesheet" type="text/css" />
<style>
.inline-element {
display: inline-block;
}
.debugger {
height: 135px;
}
.monitor {
height: 360px;
}
</style>There are four indexed addressing modes on the MOS 6502. I’ve found the last one, indirect indexed, the most useful in my high-res graphics mode experiments on the Apple II+, but wanted to write about them all a bit. There are interactive examples at the end of this post (requires Javascript).NSWorkspace NotificationCenter observers in pure Swift2023-03-10T22:49:57+00:002023-03-10T22:49:57+00:00https://maddie.info//appledev/2023/03/10/nsworkspace-notificationcenter-observers-in-swift<p>For a recent MacOS project I had to hook into the system screen wake and sleep notifications. This is
handled with the notification center in NSWorkspace.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">center</span> <span class="o">=</span> <span class="kt">NSWorkspace</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">notificationCenter</span><span class="p">;</span>
<span class="k">let</span> <span class="nv">mainQueue</span> <span class="o">=</span> <span class="kt">OperationQueue</span><span class="o">.</span><span class="n">main</span><span class="p">;</span>
<span class="n">center</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="nv">forName</span><span class="p">:</span> <span class="kt">NSWorkspace</span><span class="o">.</span><span class="n">screensDidWakeNotification</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">queue</span><span class="p">:</span> <span class="n">mainQueue</span><span class="p">)</span> <span class="p">{</span> <span class="n">notification</span> <span class="k">in</span>
<span class="nf">screensChangedSleepState</span><span class="p">(</span><span class="n">notification</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">center</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="nv">forName</span><span class="p">:</span> <span class="kt">NSWorkspace</span><span class="o">.</span><span class="n">screensDidSleepNotification</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">queue</span><span class="p">:</span> <span class="n">mainQueue</span><span class="p">)</span> <span class="p">{</span> <span class="n">notification</span> <span class="k">in</span>
<span class="nf">screensChangedSleepState</span><span class="p">(</span><span class="n">notification</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">screenChangedSleepState</span><span class="p">(</span><span class="n">_</span> <span class="nv">notification</span><span class="p">:</span> <span class="kt">Notification</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span><span class="p">(</span><span class="n">notification</span><span class="o">.</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="kt">NSWorkspace</span><span class="o">.</span><span class="nv">screensDidSleepNotification</span><span class="p">:</span>
<span class="n">socket</span><span class="p">?</span><span class="o">.</span><span class="nf">disconnect</span><span class="p">()</span>
<span class="k">case</span> <span class="kt">NSWorkspace</span><span class="o">.</span><span class="nv">screensDidWakeNotification</span><span class="p">:</span>
<span class="k">self</span><span class="o">.</span><span class="nf">openWebsocket</span><span class="p">()</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>There are two implementations of <code class="language-plaintext highlighter-rouge">addObserver</code>. The <a href="https://developer.apple.com/documentation/foundation/notificationcenter/1415360-addobserver">first</a> takes a <code class="language-plaintext highlighter-rouge">#selector</code> argument and depends on an <code class="language-plaintext highlighter-rouge">@objc</code> callback. The <a href="https://developer.apple.com/documentation/foundation/notificationcenter/1411723-addobserver">second</a> takes slightly different arguments, ending with a callback closure that does not depend on <code class="language-plaintext highlighter-rouge">@objc</code>.</p>For a recent MacOS project I had to hook into the system screen wake and sleep notifications. This is handled with the notification center in NSWorkspace.