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"](featureorfeature:operation;targets.<name>.permissionsmay add or remove with-id). Importingzinc: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.jsonis the table (op, permission, argument and result shapes, simulator answer).tools/system-opsgeneratesops.d.tsandnative/ops.gen.h;tests/t0/system_ops.shfails on drift. - Recording simulator (the backend in deterministic and headless runs): every call prints
[system] <op> <json>;ZINC_SYSTEM_LOG=-|filechooses where.ZINC_SYSTEM_SCRIPT=filedelivers 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 whoseisSupported()is false. - Phones (ZN-628): the same op table, with an iOS backend (
native/system.ios.mm) and an Android one (native/system.android.cppover the Java classnative/android/SystemBackend.java, also the activity’s lifecycle subscriber);system.backendisiosorandroid. They do local notifications (notification.notify,requestPermission: Android 13’s prompt and iOS’s;cancel; a tap isnotification-click),zinc:system/share(share.share: the system’s share sheet, the permissionshare),opener.open(a URL in the app that opens it; React Native’sLinking.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, soopen()waits for the eventdialog-result, and the paths are copies in the app’s cache) and deep links:zinc.jsonapp.urlSchemesbecomes the Android intent filter and iOS’sCFBundleURLTypes;deep-link.getCurrentholds the link that started an Android app, later links (and on iOS every link, which SDL delivers as a dropped URL) areopen-urlevents. Haptics arezinc:haptics. The other ops keep the simulator’s answer there. Proven on the iOS 26 simulator and an Android emulator (API 37, arm64) withtests/data/mobile-services(SERVICES_DO=share|open|notify|pick|photo).