zinc:system

Desktop integration for macOS and Linux apps: notifications, menus, tray, dialogs, window control, shortcuts, deep links, autostart, dock, power, clipboard, opener. Design and per-platform choices: docs/reports/system-integration.md.

import * as system from 'zinc:system';
system.call('notification.notify', { title: 'Build done' });   // needs "notification" in zinc.json permissions
system.on('menu-click', (args) => console.log(args[0]));
  • Permissions (deny by default): zinc.json "permissions": ["notification", "tray"] (feature or feature:operation; targets.<name>.permissions may add or remove with -id). Importing zinc:system/<feature> without it is error Z5006; the native side refuses an op whose id was not compiled in even when the program got past the compiler.
  • Ops: plugins/system/ops.json is the table (op, permission, argument and result shapes, simulator answer). tools/system-ops generates ops.d.ts and native/ops.gen.h; tests/t0/system_ops.sh fails on drift.
  • Recording simulator (the backend in deterministic and headless runs): every call prints [system] <op> <json>; ZINC_SYSTEM_LOG=-|file chooses where. ZINC_SYSTEM_SCRIPT=file delivers events, one line <tick> <event> args... (a tick is one pass of the event loop, a frame in a UI app): menu-click, tray-click, notification-click|action|reply|close, shortcut, drop, open-url, second-instance, power, appearance, window, dialog-answer. Unknown events are logged as errors.
  • The macOS and Linux backends and the feature modules (zinc:system/tray, …) arrive task by task (ZN-235..); until then those modules are stubs whose isSupported() is false.
  • Phones (ZN-628): the same op table, with an iOS backend (native/system.ios.mm) and an Android one (native/system.android.cpp over the Java class native/android/SystemBackend.java, also the activity’s lifecycle subscriber); system.backend is ios or android. They do local notifications (notification.notify, requestPermission: Android 13’s prompt and iOS’s; cancel; a tap is notification-click), zinc:system/share (share.share: the system’s share sheet, the permission share), opener.open (a URL in the app that opens it; React Native’s Linking.openURL), the pickers (dialog.open: Android’s document picker, iOS’s document picker or, when every filter is an image type, the photo picker; the answer comes later, so open() waits for the event dialog-result, and the paths are copies in the app’s cache) and deep links: zinc.json app.urlSchemes becomes the Android intent filter and iOS’s CFBundleURLTypes; deep-link.getCurrent holds the link that started an Android app, later links (and on iOS every link, which SDL delivers as a dropped URL) are open-url events. Haptics are zinc:haptics. The other ops keep the simulator’s answer there. Proven on the iOS 26 simulator and an Android emulator (API 37, arm64) with tests/data/mobile-services (SERVICES_DO=share|open|notify|pick|photo).

Edit this page on GitHub ↗