go-macos

macOS system APIs from pure Go β€” the Objective-C runtime, the Keychain and the notification bus, reached with CGO_ENABLED=0 and no subprocesses.

CGO_ENABLED=0 ebitengine/purego no osascript no /usr/bin/security BSD-3-Clause
GitHub Go reference

Everything here reaches the operating system the same way: ebitengine/purego β€” dlopen, dlsym and objc_msgSend β€” so the packages link with no cgo and cross-compile like any other Go code. Nothing shells out: not to osascript, not to /usr/bin/security. That last one is a security property rather than a preference β€” a secret passed to a subprocess is visible in that process's argv.

One shared home, not three copies. objc exists because the fleet's CGO-free macOS code kept re-implementing the same selector caching, NSString bridging and autorelease-pool handling. keychain and notify build on it rather than repeating it, and the counts below are measured from the go.mod files that require each package.

Repositories

objc foundation

dlopen + objc_msgSend, via ebitengine/purego

The shared home for the Objective-C plumbing the fleet's CGO=0 macOS code kept re-implementing: cached selector/class lookup, NSString↔Go bridging, framework dlopen (Foundation, AppKit, WebKit, CoreFoundation, Security, libSystem), autorelease pools, run loops, runtime class registration, and a main-thread hop through libdispatch.

every other macOS package here rests on it

CI Go reference BSD-3-Clause

keychain secret store

dlsym'd CoreFoundation + Security, via ebitengine/purego

Generic-password items over a (service, account) pair β€” Set, Get, Delete β€” with typed errors comparable through errors.Is / errors.As. It never shells out to /usr/bin/security, so the secret never appears in any process's argv.

6 repositories in the fleet require it

CI Go reference BSD-3-Clause

notify notifications

Darwin notify(3), NSDistributedNotificationCenter, NSUserNotificationCenter

Three mechanisms behind one package: named cross-process signals with no payload, distributed notifications carrying a map[string]string userInfo, and user-visible notifications. Each row of its table states plainly whether it is cross-process and whether it needs a run loop.

the macOS counterpart to go-freedesktop/notifications

CI Go reference BSD-3-Clause

screencapture capture

ScreenCaptureKit, via ebitengine/purego

Enumerate displays and windows and stream their pixels as borrowed BGRA, allocation-free per frame. Frames arrive only when something changes, as ScreenCaptureKit is change-driven. Capturing another process's content needs the Screen Recording grant and says so in the error; capturing your OWN window needs no grant at all, which is how its live tests prove themselves on a machine that never granted one.

go-xrkit/desk streams the screens it draws through it

CI Go reference BSD-3-Clause

virtualdisplay displays

PRIVATE CoreGraphics CGVirtualDisplay classes, via ebitengine/purego

Displays macOS extends the desktop onto that no cable is attached to β€” real CGDirectDisplayIDs, in System Settings, capturable. It rests on UNDOCUMENTED CoreGraphics classes: Apple may remove them in any release, and a program linking it cannot ship on the Mac App Store. It never sends a message it has not first verified, so a missing class is an error and not a crash.

go-xrkit/desk creates the screens it puts on the ribbon

CI Go reference BSD-3-Clause

iokit devices

IOKit HID, via ebitengine/purego

HID device enumeration, report writing and input-report streaming, with no cgo, no hidapi and no shelling out to ioreg.

how the XR glasses' HID interfaces were surveyed; nothing in the fleet requires it yet

CI Go reference BSD-3-Clause

avfoundation video decode

AVAssetReader, via ebitengine/purego

Hardware-accelerated video decoding through the AVAssetReader pull API: zero-copy BGRA frames carrying their presentation timestamps.

go-xrkit/player decodes film through it

CI Go reference BSD-3-Clause

videotoolbox video decode

VideoToolbox, via ebitengine/purego

Hardware H.264 and HEVC decoding β€” the path AVFoundation cannot take, because AVFoundation does not demux Matroska.

go-xrkit/player, for the containers AVFoundation will not open

CI Go reference BSD-3-Clause

audiotoolbox audio

AudioToolbox, via ebitengine/purego

Decode compressed audio and play PCM, CGO_ENABLED=0 throughout.

go-xrkit/player plays sound through it

CI Go reference BSD-3-Clause

accessibility windows

Accessibility (AX) API, via ebitengine/purego

Move another application's window to a chosen display. It needs the Accessibility grant and says so plainly rather than failing silently, and it never trusts a write: every move is read back, and a window that did not go where it was told is a refusal with both frames in the message. Two rules in it came from measurement, not documentation β€” a window is SHRUNK BEFORE being moved, because the window server clamps a move by the size the window has at the moment of the write, and a window too big for its destination is walked one display closer per write rather than refused, so it keeps writing while each write brings the window nearer.

go-xrkit/desk puts applications on the ribbon's screens with it

CI Go reference BSD-3-Clause

hotkey shortcuts

Carbon RegisterEventHotKey, via ebitengine/purego

System-wide keyboard shortcuts with NO permission dialog at all β€” Carbon's hot-key API is the one path on macOS that needs neither Accessibility nor Input Monitoring. When the combination you asked for is already taken it falls back to a neighbour and tells you, in glyphs, which one it got, so a program can print what a person should actually press.

go-xrkit/desk claims fourteen of them

CI Go reference BSD-3-Clause

statusitem menu bar

NSStatusBar, NSStatusItem and NSMenu, via ebitengine/purego

An item in the menu bar β€” what everyone calls a tray icon β€” from pure Go. A row carries a title, an optional key equivalent and a Go func; an empty row is a separator. It matters most for a program with no window of its own, or one whose window is not there yet: the menu is where a person clicks when there is nothing else to click.

go-xrkit/desk hangs its πŸ‘“ menu there

CI Go reference BSD-3-Clause

pointer pointer

CGWarpMouseCursorPosition and CGEventGetLocation, via ebitengine/purego

Says where the pointer is and puts it somewhere else, with no permission of any kind. It exists because of a measured failure: an application that shows one display inside another gives a person a picture of somewhere the pointer can go and no way to get it there, and dragging the mouse across a captured screen is dragging it blind. It moves the pointer and does not click β€” nothing here synthesises a button event.

go-xrkit/desk brings the pointer to the screen you are looking at

CI Go reference BSD-3-Clause

fileprogress progress

NSProgress published to the system, via ebitengine/purego

Tells macOS a file is being downloaded, so the Finder draws the pie it draws for a browser's downloads. The pie is not something the system works out by watching a file grow: it has to be PUBLISHED, and there is exactly one way of being heard. An extended attribute on the file looks like it ought to work, is documented in various corners of the internet, and does nothing β€” measured against a real Finder before this package was written.

nothing in the fleet yet

CI Go reference BSD-3-Clause

appicon icons

NSRunningApplication and NSBitmapImageRep, via ebitengine/purego

A running application's own icon, as straight RGBA, at the size you ask for β€” and no permission of any kind, because an icon is public information about a process where its windows are not. A size is asked for rather than inferred: an icon is a FAMILY of representations, 32 of them for Firefox, so asking for 64 gets the 64 the designer drew where scaling "the" icon gets a blurred 512. Three measured traps are documented beside the lines they explain β€” NSRunningApplication answers from a cache that needs the run loop pumped, a class that was never loaded answers zero rather than erroring, and a bitmap context cannot draw non-premultiplied, which makes graphicsContextWithBitmapImageRep: return nil.

go-xrkit/desk shows what is running, with the icons

CI Go reference BSD-3-Clause

appbundle bundles

the .app directory itself β€” path and plist work, no AppKit

Says whether the running process is inside an .app, and assembles one around an executable. It touches no framework at all, which is the point: a bundle assembler is needed in a BUILD, on whatever machine the build runs on, so this one compiles and is tested on linux and windows too. What it encodes is that a bare executable is not an application on this system β€” asked for from outside a bundle, a status item is asked for by nobody, appears nowhere, and the process ends without complaining.

go-downloader/godl ships as an .app built by it

CI Go reference BSD-3-Clause

brightness backlight

DisplayServicesGet/SetBrightness β€” a PRIVATE framework, looked up at run time

Reads and sets a display's backlight, and hands back the way home. It exists for the screen that is being shown SOMEWHERE ELSE: mirrored into a pair of display glasses, the physical panel is a copy of private work at reading distance, facing whoever walks past, lit at full power for nobody. Turning the backlight off beats covering the screen with a black window β€” the framebuffer is untouched, so a capture of that display is unchanged, and there is no window for something else to raise itself above. Two things are documented because they were measured: the documented IODisplay route does not drive an Apple Silicon built-in panel at all, and DisplayServicesSetBrightness answers success for a display id no machine has, which is why Dim READS before it changes anything.

go-xrkit/desk darkens the Mac's panel while the glasses show it

CI Go reference BSD-3-Clause

launchagent start at login

a plist in ~/Library/LaunchAgents, or SMAppService through go-macos/servicemanagement

Keeps a program running across logouts and restarts, by whichever of the two mechanisms this process can actually use β€” SMAppService inside an application bundle, the legacy plist everywhere else, and it says which one it used. A refusal falls back AND reports itself, because failing outright would stop a working program working the day it gained a bundle, and falling back silently would hide a real packaging defect for as long as anybody cared to look. Disable takes away BOTH: a program that shipped a plist and then gained a bundle has two registrations, macOS acts on the newer one, and removing only that is the bug a person describes as "I turned it off and it came back".

go-downloader/godl, behind `godl config startup on`

CI Go reference BSD-3-Clause

servicemanagement login items

SMAppService, via ebitengine/purego

The supported way to start an application, agent or daemon at login since macOS 13. Two things it is shaped around, both measured rather than read: requiresApproval is a NORMAL outcome β€” register succeeds, the service is held, and it will not run until a person allows it in System Settings β€” so the status is a value carrying the sentence to show them, not an error and not a silence. And SMAppService needs a BUNDLE, which macOS hides: outside one the class is there, the factories hand back objects, and status answers notFound exactly as a genuinely missing service does; only register fails, blaming code signing ("Codesigning failure loading plist … code: -67028"). So every call asks NSBundle for an identifier first and reports ErrNotBundled, which is answerable.

go-macos/launchagent prefers it over the plist whenever the caller is bundled

CI Go reference BSD-3-Clause

appkit native widgets

AppKit's own controls, via go-macos/objc

Real NSButton, NSTextField, NSSecureTextField and NSSlider inside a host's own NSView. A secure field is the clearest case: what makes it secure is that the system, not the application, owns the keystrokes β€” so drawing dots into a canvas is not the same thing, however similar it looks.

go-widgets/toolkit, for the controls a pixel blit cannot honestly imitate

CI Go reference BSD-3-Clause

diskarbitration storage

DiskArbitration

Which block devices exist, which volume is mounted where, and how to take one away politely. No hdiutil, no diskutil, no plist parsing. It reaches 100% coverage of the BINDINGS by making the bound C entry points the seams, so DASessionCreate can be made to answer NULL without a broken machine.

no consumer wired yet β€” written ahead of its caller, which this org's doctrine says not to do

CI Go reference BSD-3-Clause

localauthentication identity

LocalAuthentication

Ask macOS to authenticate the person at the keyboard β€” Touch ID, or the login password. Biometry is the one thing that cannot be reimplemented: it is an attestation by the Secure Enclave, and a pure-Go substitute would be a lie.

go-pdfkit's reader, to unlock a vault with Touch ID

CI Go reference BSD-3-Clause

usernotifications notifications

UserNotifications, CoreServices

UNUserNotificationCenter β€” the framework the system actually uses, replacing the deprecated NSUserNotification. No osascript. A notification needs a real application bundle to come from, which is why go-macos/appbundle sits beside it.

the news reader, and anything that must tell a person something while it is not in front of them

CI Go reference BSD-3-Clause

multitouch input

PRIVATE MultitouchSupport, via ebitengine/purego

The raw contacts on a Mac's trackpad, with NO permission at all. The gestures macOS publishes are the ones macOS has already decided about: a three-finger swipe means switch space only if that is switched on, it arrives as a private event the Dock consumes first, and an application that wants it for something else has to ask a person to change a system setting so that a program can work β€” which is backwards. The contacts underneath are not spoken for.

go-xrkit/desk switches screen on a three-finger swipe

CI Go reference BSD-3-Clause

metal GPU compute

Metal

Compute kernels on the GPU, compiled from Metal Shading Language at RUN TIME by the system compiler β€” no build step, no toolchain, nothing shipped but a source string. On a chip with unified memory a buffer's bytes are the bytes the GPU reads, so nothing is uploaded or downloaded. Measured on an M4 Max, a 4K frame of image work: 65 ms and 82 ms of processor time on sixteen cores, against 4 ms and 0.16 ms here.

go-xrkit/player synthesises both eyes of a converted film

CI Go reference BSD-3-Clause

coreml neural engine

CoreML, CoreVideo

A trained model on the Neural Engine, with no cgo and no Xcode β€” an .mlpackage is compiled by the system compiler at run time. It is NOT the fastest of the three processors; the GPU is, by nearly half. It is the one that leaves the machine alone: 0.4 ms of processor time a frame against 114 ms on the CPU. So Open makes the caller choose rather than hiding a default.

go-xrkit/player estimates depth, to turn a flat film into 3D

CI Go reference BSD-3-Clause

These are macOS-side packages, so they build and test on darwin; on every other platform the exported calls return a typed ErrUnsupported rather than failing to compile. go-macos/objc has a deliberate peer on the other side of the fence β€” go-mswin/win32 β€” so a program that needs both talks to two libraries built the same way, instead of one library and a pile of hand-rolled syscalls.