Hand-written FFI bindings for macOS C frameworks that supplement objc-js and objcjs-types.
objc-js auto-generates bindings for Objective-C classes, protocols, and enums. Pure-C frameworks like CoreFoundation, CoreGraphics, and the Accessibility API have no ObjC metadata to generate from, so this package provides them manually.
Bun & Node.js — This package works with both Bun (
bun:ffi) and Node.js (koffi). The runtime is detected automatically at import time.
Bun:
bun add objcjs-extra objc-js objcjs-typesNode.js:
npm install objcjs-extra objc-js objcjs-types koffiOn Node.js, the koffi package (>= 2.0) is required for FFI support. It is listed as an optional dependency and must be installed explicitly.
All framework modules import from a unified FFI abstraction layer (src/modules/ffi.ts) instead of bun:ffi directly. This module:
- Detects the runtime (
globalThis.Bun) and selects the appropriate backend - On Bun, delegates to
bun:ffi(dlopen,ptr,read,JSCallback) - On Node.js, delegates to
koffi(koffi.load,koffi.decode,koffi.register) - Exports a unified API:
dlopen,ptr,read,FFIType,JSCallback,createCallback
Framework bindings are portable across both runtimes with no code changes needed.
Each framework is a separate entry point, loaded lazily on first use.
| Framework | Description |
|---|---|
| CoreFoundation | CF memory management, CFString, CFData, CFRunLoop, CFMachPort |
| CoreGraphics | Quartz Event API, window list/capture, display management, bitmap contexts, color spaces |
| ApplicationServices | AXUIElement accessibility API, AXObserver, AXValue |
| Security | Keychain Services, Authorization, code signing, certificates, SecRandom |
| CoreServices | FSEvents (file watching), Launch Services (open apps/URLs) |
| IOKit | Service matching, registry properties, power assertions (prevent sleep) |
| CoreText | Font creation/metrics, font collections, descriptors, line layout |
| ImageIO | CGImageSource (read), CGImageDestination (write), format queries, EXIF metadata |
| CoreAudio | Audio device enumeration, volume/mute control, sample rate, HAL properties |
| Network | Endpoints, parameters, connections, listeners, dispatch queues |
| CoreMedia | CMTime (pure JS arithmetic), CMSampleBuffer, CMFormatDescription |
| Accelerate | vDSP (float/double vector ops), CBLAS (matrix multiply, norms) |
Memory management, CFString, CFData, CFRunLoop, and CFMachPort.
import {
CFStringCreateWithJSString,
CFStringGetJSString,
CFRelease,
CFRunLoopGetCurrent,
CFRunLoopRun
} from "objcjs-extra/CoreFoundation";Synthetic mouse, keyboard, and scroll-wheel events via the Quartz Event API. Also includes window list/capture, display management, bitmap contexts, and color spaces.
import {
CGEventCreateKeyboardEvent,
CGEventPost,
CGEventTapLocation,
CGEventSourceCreate,
CGEventSourceStateID
} from "objcjs-extra/CoreGraphics";
const source = CGEventSourceCreate(CGEventSourceStateID.CombinedSessionState);
const keyDown = CGEventCreateKeyboardEvent(source, 0x00, true); // 'a' key
CGEventPost(CGEventTapLocation.SessionEventTap, keyDown);macOS Accessibility API (AXUIElement) for UI inspection and automation.
import {
AXIsProcessTrusted,
AXUIElementCreateApplication,
AXUIElementCopyAttributeNames,
AXUIElementCopyAttributeString,
AXUIElementPerformAction,
kAXTitleAttribute,
kAXPressAction,
kAXFocusedWindowAttribute
} from "objcjs-extra/ApplicationServices";
if (!AXIsProcessTrusted()) {
console.error("Enable Accessibility in System Settings > Privacy & Security");
process.exit(1);
}
const app = AXUIElementCreateApplication(pid);
const title = AXUIElementCopyAttributeString(app, kAXTitleAttribute);
console.log("App title:", title);Includes typed convenience helpers (AXUIElementCopyAttributeString, AXUIElementCopyAttributePoint, etc.), AXValue boxing/unboxing for CGPoint/CGSize/CGRect/CFRange, and AXObserver for watching accessibility notifications.
Keychain Services, Authorization, code signing, certificates, and secure random number generation.
import {
SecItemAdd,
SecItemCopyMatching,
SecItemUpdate,
SecItemDelete,
SecRandomBytes,
SecCodeCopySelf,
SecCodeCheckValidity,
errSecSuccess,
errSecItemNotFound
} from "objcjs-extra/Security";
// Generate 32 bytes of cryptographically secure random data
const randomData = SecRandomBytes(32);FSEvents for file system monitoring and Launch Services for opening apps/URLs.
import {
createFSEventStream,
FSEventStreamStart,
FSEventStreamStop,
FSEventStreamRelease,
getApplicationURLsForBundleId,
kFSEventStreamCreateFlagFileEvents,
kFSEventStreamCreateFlagNoDefer
} from "objcjs-extra/CoreServices";
// Watch a directory for file-level changes
const stream = createFSEventStream(
["/path/to/watch"],
(paths, flags, ids) => {
console.log("Changed:", paths);
},
kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer
);
// Find applications by bundle identifier
const urls = getApplicationURLsForBundleId("com.apple.Safari");Service matching, registry property queries, and power management assertions.
import {
IOServiceMatching,
IOServiceGetMatchingService,
IORegistryEntryCreateCFProperty,
IOObjectRelease,
preventSleep,
preventDisplaySleep,
kIOMasterPortDefault
} from "objcjs-extra/IOKit";
// Prevent the system from sleeping
const assertion = preventSleep("Long-running operation");
// ... do work ...
assertion.release();
// Prevent just the display from sleeping
const displayAssertion = preventDisplaySleep("Presentation mode");
// ... do work ...
displayAssertion.release();Font creation, metrics, descriptors, font collections, and line layout.
import {
CTFontCreateWithName,
CTFontCopyFamilyName,
CTFontGetSize,
CTFontGetAscent,
CTFontGetDescent,
CTFontGetLeading,
getAvailableFontFamilies
} from "objcjs-extra/CoreText";
// List all available font families on the system
const families = getAvailableFontFamilies();
console.log("Available fonts:", families.length);Read and write image data in various formats (JPEG, PNG, TIFF, HEIF, etc.) via CGImageSource and CGImageDestination.
import {
CGImageSourceCreateWithData,
CGImageSourceGetCount,
CGImageSourceGetStatus,
CGImageSourceCopyPropertiesAtIndex,
CGImageDestinationCreateWithData,
CGImageDestinationAddImage,
CGImageDestinationFinalize,
CGImageSourceStatus
} from "objcjs-extra/ImageIO";Audio Hardware Abstraction Layer (HAL) for device enumeration, volume control, mute state, and property listeners.
import {
getDefaultOutputDevice,
getDefaultInputDevice,
getAudioDevices,
getDeviceName,
getDeviceVolume,
setDeviceVolume,
getDeviceMute,
setDeviceMute
} from "objcjs-extra/CoreAudio";
// Get the default output device and its volume
const device = getDefaultOutputDevice();
const name = getDeviceName(device);
const volume = getDeviceVolume(device);
console.log(`${name}: volume ${(volume * 100).toFixed(0)}%`);
// Set volume to 50%
setDeviceVolume(device, 0.5);macOS Network.framework for endpoint, parameter, connection, and listener management.
import {
nwEndpointCreateHost,
nwEndpointGetHostname,
nwEndpointGetPort,
nwParametersCreateSecureTCP,
nwConnectionCreate,
nwConnectionStart,
nwListenerCreateWithPort,
nwListenerStart,
nwRelease
} from "objcjs-extra/Network";Note: Many Network.framework APIs use Objective-C block-based callbacks (e.g. state change handlers, send/receive). These blocks are not simple C function pointers, so
JSCallbackcannot be used for them. For full async Network usage, consider using an ObjC bridge or Swift helper library.
CMTime arithmetic (pure JS), CMSampleBuffer, and CMFormatDescription.
import {
CMTimeMake,
CMTimeMakeWithSeconds,
CMTimeGetSeconds,
CMTimeAdd,
CMTimeSubtract,
CMTimeCompare,
CMTimeIsValid,
CMSampleBufferGetNumSamples,
CMFormatDescriptionGetMediaType,
fourCCToString,
kCMTimeZero
} from "objcjs-extra/CoreMedia";
// CMTime is implemented in pure JS — no FFI overhead
const time1 = CMTimeMake(3000, 600); // 5.0 seconds
const time2 = CMTimeMakeWithSeconds(2.5, 600);
const sum = CMTimeAdd(time1, time2);
console.log("Total:", CMTimeGetSeconds(sum), "seconds");Note: CMTime is a 24-byte value type. Because FFI cannot return structs by value, the CMTime API is implemented in pure JavaScript with buffer serialization helpers for native interop.
High-performance vector and matrix operations via vDSP and CBLAS.
import {
vaddD,
vsubD,
vmulD,
vdivD,
dotProductD,
meanD,
normalizeD,
vaddF,
vmulF,
dgemm,
dnrm2,
CblasRowMajor,
CblasNoTrans
} from "objcjs-extra/Accelerate";
// Vector addition (double-precision)
const a = new Float64Array([1.0, 2.0, 3.0]);
const b = new Float64Array([4.0, 5.0, 6.0]);
const sum = vaddD(a, b); // Float64Array [5.0, 7.0, 9.0]
// Dot product
const dot = dotProductD(a, b); // 32.0
// Matrix multiply (CBLAS dgemm)
const A = new Float64Array([1, 2, 3, 4, 5, 6]); // 2x3
const B = new Float64Array([7, 8, 9, 10, 11, 12]); // 3x2
const C = dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 2, 2, 3, 1.0, A, B, 0.0);objcjs-extra is designed to work alongside objc-js. Use objc-js for Objective-C APIs (NSWorkspace, NSRunningApplication, etc.) and objcjs-extra for the underlying C APIs:
import { objc } from "objc-js";
import {
CFRunLoopGetCurrent,
CFRunLoopAddSource,
CFRunLoopRun,
kCFRunLoopDefaultMode
} from "objcjs-extra/CoreFoundation";
import {
AXObserverCreate,
AXObserverGetRunLoopSource,
AXObserverAddNotification,
AXUIElementCreateApplication,
kAXFocusedWindowChangedNotification
} from "objcjs-extra/ApplicationServices";
const workspace = objc.classes.NSWorkspace.sharedWorkspace();
const frontApp = workspace.frontmostApplication();
const pid = frontApp.processIdentifier();
const observer = AXObserverCreate(pid, (obs, el, notification) => {
console.log("Notification:", notification);
});
const appElement = AXUIElementCreateApplication(pid);
AXObserverAddNotification(observer, appElement, kAXFocusedWindowChangedNotification);
const source = AXObserverGetRunLoopSource(observer);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRunLoopRun();bun run build # or: npx tsc