Annotator is a free, open-source app for drawing on your screen while you explain something. You pick a tool from a small floating toolbar and draw over any app: circle a number, bend an arrow to a button, highlight a line. The marks stay on screen while you keep working in the app underneath, and you can move, restyle or undo them later.
Arrows are placed point by point, so you can bend them around things. Smart Draw turns a quick sketch into a neat box or arrow.
It also zooms around the pointer, puts a spotlight on it, blurs a busy picture for a moment, and shares one part of your screen in a meeting with your drawings in it. There are about 40 tools in all, and there's a short demo on getannotator.app.
No account, nothing leaves your computer, and usage counts are off unless you turn them on.
Why two native apps
It started on the Mac in Swift. When people asked for Windows, I had to decide between a cross-platform UI toolkit and a second native app.
I went native twice. The overlay has to sit above every other window, pass clicks through to the app underneath, and read the screen for zoom and blur. Those are the parts where each OS has its own rules. So the Mac app is Swift, AppKit and Metal, and the Windows app is C# on .NET 10 with WinUI 3, compiled ahead of time with Native AOT.
Two codebases drift apart. Here's what keeps them together, and how one GitHub Actions run ships both.
The Mac is the reference
Every behavior is defined by the Mac app first. The Windows app matches it, and "matches" has to be something a test can check, not a feeling.
A conformance corpus. A small Swift tool, ConformanceExport, runs the Mac's annotation core and writes the results to reference/conformance: how a shape is stored, where a hit test lands, how a rough ellipse is drawn for a given seed. A manifest records a SHA-256 for every file. The Windows test project reads the same files and runs the C# port against them. If the Mac changes a rule and nobody ports it, the Windows tests fail.
A UX reference. The Mac's native UI tests can capture each panel and menu: labels, accessibility identifiers, values, order and frames. A script packs those into reference/ux-reference. The Windows tests compare their own surfaces with that capture, within 1 DIP, with exact labels and order.
Both folders are generated on the Mac and never edited by hand. A Mac change that affects them regenerates them in the same pull request, so the diff shows exactly what Windows now has to match.
Plain rules in review. Every user-visible change ships on both platforms in the same release. If something can't work the same way on Windows, it gets a written deviation in the parity docs instead of a silent difference.
One commit, every channel
A release has a lot of destinations: a signed and notarized Mac disk image and ZIP, a Sparkle update feed, a Homebrew cask, a Microsoft Store bundle, portable Windows ZIPs for winget, and the website. I wanted all of them to come from one commit, built once.
The release notes live in releases/notes.json. That one file produces the changelog, the GitHub release text and the app's own "What's changed" screen. When a version is ready, its entry records the exact source commit. A check then fails if any app code changed after that commit, so the thing I tested is the thing I ship.
The Release workflow only starts by hand, on main. It runs, roughly:
- Preflight checks that the commit, the version and the frozen notes agree, and stops before anything is built if they don't.
- Validate runs the release and packaging guards.
- Sign builds the Mac app, signs it with a Developer ID, notarizes and staples it, and builds the disk image.
-
Windows packages builds the x64 and ARM64 Native AOT packages, the MSIX bundle and the portable ZIPs. The Windows package version is derived from the shared one,
(Major + 1).Minor.Build.0, because the Store wants a version that only goes up. - Publish creates the GitHub release and updates the Homebrew tap.
- Store sends the bundle to the Microsoft Store.
After that, a website workflow deploys the site and the update feeds from the published release.
Secrets sit in GitHub environments, each visible only to the job that needs it, and each environment waits for my approval. Pull requests, including ones from forks, get no secrets at all.
Things that bit me
- A signing step failed with "a base64 signing secret is invalid". The secret it pointed at was fine. The real problem was a public key variable missing its final
=. Now I check every value when signing fails, not only the one I suspect. - The pull request checks used
cancel-in-progress. Opening a labelled pull request fires two events at once, one run got cancelled, and its cancelled checks blocked the merge. The checks take seconds, so every event now gets its own run. - The Microsoft Store CLI refuses every command until it has been signed in with
reconfigure, even setting its own options. The order matters.
Try it, or help
Annotator is free and Apache-2.0: https://github.com/dorlugasigal/annotator. On the Mac, brew install --cask dorlugasigal/annotator/annotator. On Windows, it's in the Microsoft Store.
The open issues are written so anyone can pick one up. Several need real hardware I don't have: an Intel Mac, an ARM64 PC, a pen display, a second screen at another scale. If you have one of those, an hour of testing helps a lot.
dorlugasigal
/
annotator
Draw on your screen as you explain. Native apps for macOS and Windows. Free and open source.
Annotator
Draw on your screen as you explain.
Use it in demos, reviews and lessons. Every mark stays editable
Native apps for macOS and Windows.
Download · Website · Help · Release notes
annotator-reel.mp4
Recorded in the real Mac app, with a scripted pointer and made-up sample apps.
Music: “Product Launch Advertising Commercial Music” by lNPLUSMUSIC, from Pixabay.
Note
Annotator is in public beta. Expect rough edges, and please report them.
What you can do
Pick a tool in the floating toolbar and draw over any app. Your marks stay on screen while you keep working in the app underneath, and you can move, restyle or undo them later.






Top comments (0)