API Documentation
Complete reference for saMacros Script Developers
This is the authoritative reference for all public APIs exposed to JavaScript scripts running inside saMacros.
All interfaces are sourced from the samacros-api module.
Entry Point
Global mm object injected at script load time.
Engine
GraalVM Polyglot JavaScript โ ES2020 compatible.
Package Base
io.github.samera2022.samacros.api
List objects use .size() (not .length) to get the element count.
Java java.awt.Point objects expose .x and .y as direct fields.
Java java.awt.Color objects expose .getRed(), .getGreen(), .getBlue() methods returning 0โ255.
Script Metadata
Every script must declare these global variables before any logic. They are parsed by ScriptDescription.java at load time.
| Variable | Type | Required | Description |
|---|---|---|---|
| display_name | string | Yes | Human-readable name shown in Script Manager UI. |
| register_name | string | Yes | Unique machine identifier (lowercase_snake_case). Used as the dependency key and internal reference. |
| author | string | Yes | Author name. Used for author-level native-access whitelisting. |
| version | string | Yes | Script version โ semantic versioning (MAJOR.MINOR.PATCH) recommended. |
| description | string | Yes | Short description shown in Script Manager. |
| available_version | string | No | Compatible saMacros version(s). Supports *, exact (2.0.0), wildcard (2.*), range (2.0.0~2.1.*). |
| hard_dependencies | string[] | No | Array of register_names that must be enabled. Script will not load if any are missing. |
| soft_dependencies | string[] | No | Optional script dependencies. Script still loads if these are missing. |
| requireNativeAccess | boolean | No | Request unrestricted JVM/filesystem access. Script is disabled on first load until user approves. |
| requireNativeAccessDescription | string | No | Explanation shown to user in the security approval dialog. Be honest and specific. |
// ==UserScript==
var display_name = "My Script";
var register_name = "my_script"; // unique, lowercase_snake_case
var author = "YourName";
var version = "1.0.0";
var description = "Does something useful.";
var available_version = "*"; // * = all versions
var hard_dependencies = ["base_library"]; // required
var soft_dependencies = ["optional_lib"]; // optional
var requireNativeAccess = false;
var requireNativeAccessDescription = "";
// ==/UserScript==
Event System
saMacros uses a priority-ordered event bus. In JavaScript, register listeners with mm.on().
Execution Order
Handlers execute in priority order:
- LOWEST โ first
- LOW
- NORMAL (default for mm.on)
- HIGH
- HIGHEST
- MONITOR โ last (observe only)
Cancellable Events
Call event.setCancelled(true) to prevent the default action:
mm.on('...BeforePlaybackStartEvent',
function(event) {
if (shouldBlock) {
event.setCancelled(true);
}
}
);
Base Event Methods (inherited by all events)
| Method | Returns | Description |
|---|---|---|
| getTimestamp() | long | Unix timestamp (ms) when the event was created. |
Cancellable events additionally expose isCancelled() โ boolean and setCancelled(boolean).
mm
โ IScriptAPI
The global mm object is the sole entry point to the saMacros API, injected into every script at load time.
| Method | Signature | Returns | Description |
|---|---|---|---|
| on | on(eventClassName, callback) | void | Register a JavaScript callback for the named event class. The callback receives the event object as its sole argument. Executes at NORMAL priority. |
| log | log(message) | void | Print a message to the application log panel, prefixed with [Script]. Non-strings are coerced via toString(). |
| getContext | getContext() | IScriptContext | Returns the IScriptContext singleton for this script, providing access to simulation, screen reading, notifications, and sub-info interfaces. |
| cleanup | cleanup() | void | Unregister all listeners created by mm.on(). Called automatically when the script is disabled. Safe to call manually for early teardown. |
mm.on(
'io.github.samera2022.samacros.api.event.events.OnAppLaunchedEvent',
function(event) {
mm.log('Hello! App version: ' + event.getAppVersion());
}
);
var ctx = mm.getContext();
ctx.showToast('Ready', 'Script loaded.');
mm.getContext()
โ IScriptContext
Provides direct interaction with the runtime. Obtain once and reuse: var ctx = mm.getContext();
| Method | Signature | Returns | Description |
|---|---|---|---|
| simulate | simulate(action) | void | Execute an IMouseAction via Java Robot โ calls action.perform(). Passing null logs a warning and does nothing. |
| getPixelColor | getPixelColor(x, y) | java.awt.Color | null | Capture screen pixel at (x,y) using java.awt.Robot. Returns null on AWTException. Use .getRed(), .getGreen(), .getBlue() (0โ255). |
| showToast | showToast(title, msg) | void | System tray balloon notification. Falls back to stdout if tray unavailable. Temporary icons are auto-removed after 5 seconds. |
| getAppConfig | getAppConfig() | IConfig | Read-only access to the application configuration. See IConfig. |
| getSystemInfo | getSystemInfo() | ISystemInfo | OS, Java version, DPI scale, dark mode, system language. See ISystemInfo. |
| getScreenInfo | getScreenInfo() | IScreenInfo | Primary screen size, monitor count, virtual origin, coordinate normalisation. See IScreenInfo. |
| getMacroInfo | getMacroInfo() | IMacroInfo | Live macro state: recording, playing, paused, action count. See IMacroInfo. |
| getMouseInfo | getMouseInfo() | IMouseInfo | Current mouse cursor position. See IMouseInfo. |
| getI18n | getI18n() | II18n | Lookup application-bundled i18n strings. See II18n. |
ctx.getSystemInfo() โ ISystemInfo
Operating system and JVM details.
| Method | Returns | Description |
|---|---|---|
| getScale() | double[] | DPI scale factors for primary screen as [scaleX, scaleY]. E.g. [1.5, 1.5] on 150% DPI. |
| isSystemDarkMode() | boolean | OS dark mode flag. Reliable on Windows 10+ only; returns false on other platforms. |
| getSystemLanguage() | string | System locale code, e.g. "en_us", "zh_cn". |
| getOSName() | string | Value of System.getProperty("os.name"), e.g. "Windows 11", "Linux". |
| getJavaVersion() | string | Value of System.getProperty("java.version"), e.g. "21.0.3". |
var sys = mm.getContext().getSystemInfo();
mm.log(sys.getOSName() + ' / Java ' + sys.getJavaVersion());
var scale = sys.getScale();
mm.log('DPI: ' + scale[0] + 'x' + scale[1]);
mm.log('Dark mode: ' + sys.isSystemDarkMode());
ctx.getScreenInfo() โ IScreenInfo
Screen geometry and multi-monitor coordinate helpers.
| Method | Returns | Description |
|---|---|---|
| getWidth() | int | Width of the primary screen in pixels. |
| getHeight() | int | Height of the primary screen in pixels. |
| getScreenCount() | int | Number of connected monitors. |
| getVirtualOrigin() | java.awt.Point | Top-left corner of the combined virtual screen (may be negative in multi-monitor setups). |
| normalizeToVirtualOrigin(x,y) | java.awt.Point | Convert primary-screen coordinates to virtual-screen (stored) coordinates. |
| denormalizeFromVirtualOrigin(x,y) | java.awt.Point | Convert stored virtual coordinates back to Robot-compatible primary-screen coordinates. |
var scr = mm.getContext().getScreenInfo();
mm.log(scr.getWidth() + 'x' + scr.getHeight() + ' (' + scr.getScreenCount() + ' monitors)');
// Convert stored coordinate for Robot on secondary monitor
var pt = scr.denormalizeFromVirtualOrigin(-1920, 540);
mm.log('Robot coords: ' + pt.x + ',' + pt.y);
ctx.getMacroInfo() โ IMacroInfo
Real-time, read-only snapshot of the macro engine state.
| Method | Returns | Description |
|---|---|---|
| isRecording() | boolean | true while the user is actively recording. |
| isPlaying() | boolean | true while playback is running (including paused state). |
| isPaused() | boolean | true when playback is paused. Implies isPlaying() === true. |
| getActionsCount() | int | Number of actions in the current macro action list. |
var m = mm.getContext().getMacroInfo();
mm.log('recording=' + m.isRecording() + ' playing=' + m.isPlaying() + ' actions=' + m.getActionsCount());
ctx.getMouseInfo() โ IMouseInfo
Real-time mouse cursor position.
| Method | Returns | Description |
|---|---|---|
| getPosition() | java.awt.Point | null | Current cursor as a Point with .x and .y. Returns null if unavailable. |
var pos = mm.getContext().getMouseInfo().getPosition();
if (pos !== null) mm.log('cursor at (' + pos.x + ', ' + pos.y + ')');
ctx.getI18n() โ II18n
Access the application's built-in localisation strings.
| Method | Returns | Description |
|---|---|---|
| get(key) | string | Translated string for key in the current language. Returns key itself if not found. |
| hasKey(key) | boolean | true if a translation exists for the given key. |
| getCurrentLanguage() | string | Active language code, e.g. "en_us", "zh_cn", "ja_jp". |
var i18n = mm.getContext().getI18n();
mm.log('UI lang: ' + i18n.getCurrentLanguage());
if (i18n.hasKey('main_frame')) mm.log(i18n.get('main_frame'));
IConfig โ Application Configuration
Read-only access to config.cfg. Obtained via ctx.getAppConfig().
| Method | Returns | Description |
|---|---|---|
| getBoolean(key) | boolean | Read a boolean setting value. |
| getInt(key) | int | Read an integer setting value. |
| getDouble(key) | double | Read a floating-point setting value. |
| getString(key) | string | Read a string setting value. |
| getKeyMap() | Map<string,string> | All configuration pairs as a key-to-string map. |
Known Configuration Keys
| Key | Type | Description |
|---|---|---|
| followSystemSettings | boolean | Inherit system language and dark-mode when true. |
| lang | string | UI language code when not following system ("en_us", "zh_cn", โฆ). |
| enableDarkMode | boolean | Dark theme flag when not following system. |
| defaultStoragePath | string | Absolute path to the directory pre-filled in Save/Load dialogs. If the path does not exist, the dialog opens at the system default instead. |
| repeatTimes | int | Playback loop count (0 = infinite). |
| quickMode | boolean | Skip inter-action delays during playback. |
IMouseAction / MouseAction
The concrete MouseAction is what you receive in event callbacks and pass to ctx.simulate().
Public Fields
| Field | Type | Description |
|---|---|---|
| x | int | Screen X coordinate (virtual-origin-normalised). |
| y | int | Screen Y coordinate (virtual-origin-normalised). |
| type | int | 1=mouse press 2=mouse release 3=mouse wheel 10=key press 11=key release |
| button | int | Mouse button: 1=left, 2=middle, 3=right. Ignored for keyboard events. |
| delay | long | Wait (ms) before this action executes during playback. |
| wheelAmount | int | Scroll notches (positive=down, negative=up). Zero for non-wheel events. |
| keyCode | int | Native OS key code for keyboard events. |
| awtKeyCode | int | AWT KeyEvent virtual key code for Robot playback. Takes precedence over keyCode when non-zero. |
| metadata | Map<string,Object> | Arbitrary key-value store for script use. Not serialised to .mmc files. |
var ctx = mm.getContext();
// Left mouse click at (960, 540)
ctx.simulate({ x:960, y:540, type:1, button:1, delay:0 });
ctx.simulate({ x:960, y:540, type:2, button:1, delay:80 });
// Scroll down 3 notches
ctx.simulate({ x:960, y:540, type:3, button:0, delay:50, wheelAmount:3 });
// Press 'A' (AWT KeyEvent.VK_A = 65)
ctx.simulate({ x:0, y:0, type:10, button:0, delay:100, wheelAmount:0, keyCode:0, awtKeyCode:65 });
ctx.simulate({ x:0, y:0, type:11, button:0, delay:50, wheelAmount:0, keyCode:0, awtKeyCode:65 });
Events Reference
All classes are in io.github.samera2022.samacros.api.event.events. Pass the fully-qualified name to mm.on().
๐ App Lifecycle Events
OnAppLaunchedEvent
Not CancellableFired after all scripts have loaded and the application has fully initialised. Typically the first event your script receives.
| Method | Returns | Description |
|---|---|---|
| getAppVersion() | string | saMacros version string, e.g. "2.0.0". |
| getRuntimeEnv() | string | Runtime identifier, e.g. "GraalVM", "JRE 21". |
mm.on('...OnAppLaunchedEvent', function(e) {
mm.log('saMacros ' + e.getAppVersion() + ' on ' + e.getRuntimeEnv());
});
OnConfigChangedEvent
Not CancellableFired whenever a settings value changes (user saves the Settings dialog).
| Method | Returns | Description |
|---|---|---|
| getConfigKey() | string | Config key that changed. |
| getOldValue() | Object | Previous value (may be null). |
| getNewValue() | Object | New value. |
mm.on('...OnConfigChangedEvent', function(e) {
mm.log(e.getConfigKey() + ' changed: ' + e.getOldValue() + ' -> ' + e.getNewValue());
});
โบ Recording Events
BeforeRecordStartEvent
CancellableFired just before the recording session begins. Cancel to prevent recording from starting.
| Method | Returns | Description |
|---|---|---|
| getStartTime() | long | Unix timestamp (ms) when recording was requested. |
| getInitialMousePos() | java.awt.Point | Mouse cursor position at the start of recording. |
OnInputCapturedEvent
CancellableFired for every mouse/keyboard input captured during recording. Cancel to discard this particular input.
| Method | Returns | Description |
|---|---|---|
| getInputType() | int | 1=MousePress, 2=MouseRelease, 3=MouseWheel, 10=KeyPress, 11=KeyRelease. |
| getKeyCode() | int | Key or button code. |
| getX() | int | Mouse X at time of capture. |
| getY() | int | Mouse Y at time of capture. |
| getDelay() | long | Milliseconds elapsed since the previous captured event. |
mm.on('...OnInputCapturedEvent', function(e) {
if (e.getInputType() === 10 || e.getInputType() === 11) {
e.setCancelled(true); // filter out keyboard inputs
}
});
OnActionAddedEvent
CancellableFired after an input passes capture and is about to be appended to the macro action list. Cancel to remove it.
| Method | Returns | Description |
|---|---|---|
| getAction() | IMouseAction | The action being added. |
| getCurrentIndex() | int | Zero-based list index where the action will be inserted. |
AfterRecordStopEvent
Not CancellableFired after the recording session ends. Provides the complete final action list.
| Method | Returns | Description |
|---|---|---|
| getFinalMacroData() | List<IMouseAction> | Unmodifiable list of all recorded actions. Use .size() (Java List method) to get the count. |
| getTotalActions() | int | Total number of recorded actions. |
mm.on('...AfterRecordStopEvent', function(e) {
mm.log('Recorded ' + e.getTotalActions() + ' actions.');
});
โถ Playback Events
BeforePlaybackStartEvent
CancellableFired just before the first action executes. Cancel to abort the entire playback.
| Method | Returns | Description |
|---|---|---|
| getMacroData() | List<IMouseAction> | Unmodifiable list of actions that will execute. Use .size() (Java List method) to get the count. |
| getRepeatCount() | int | Loop count. -1 for infinite. |
BeforeStepExecuteEvent
CancellableFired before each individual action during playback. Cancel to skip that step (recorded as SKIPPED in AfterStepExecuteEvent).
| Method | Returns | Description |
|---|---|---|
| getStepIndex() | int | Zero-based index in the action list. |
| getAction() | IMouseAction | The action about to execute. |
| isLastStep() | boolean | true when this is the final action in the current loop iteration. |
AfterStepExecuteEvent
Not CancellableFired after each action completes.
| Method | Returns | Description |
|---|---|---|
| getStepIndex() | int | Zero-based step index. |
| getExecutionStatus() | string | "SUCCESS", "FAILED", or "SKIPPED". |
OnLoopCompleteEvent
Not CancellableFired after all actions in one loop iteration have executed.
| Method | Returns | Description |
|---|---|---|
| getCompletedIterations() | int | Number of iterations completed so far. |
| getRemainingIterations() | int | Remaining iterations. -1 for infinite loops. |
mm.on('...OnLoopCompleteEvent', function(e) {
mm.log('Loop ' + e.getCompletedIterations() + ' done, ' + e.getRemainingIterations() + ' left.');
});
OnPlaybackAbortedEvent
Not CancellableFired when playback is interrupted before completing all loops.
| Method | Returns | Description |
|---|---|---|
| getAbortReason() | string | Human-readable reason, e.g. "INTERRUPTED", "EXCEPTION: ...". |
| getAtStepIndex() | int | Zero-based step index where the abort occurred. |
๐พ File I/O Events
OnMacroLoadEvent
Not CancellableFired after a .mmc file has been read from disk and parsed into the action list.
| Method | Returns | Description |
|---|---|---|
| getFile() | java.io.File | The loaded file. Use .getAbsolutePath() for the path string. |
| getRawContent() | string | Raw UTF-8 file content. |
OnMacroSaveEvent
Not CancellableFired after the macro action list has been serialised and written to disk.
| Method | Returns | Description |
|---|---|---|
| getFile() | java.io.File | The target file that was written. |
| getFormattedContent() | string | The CSV content written to the file. |
๐ผ UI Events
OnMenuInitEvent
Not CancellableFired when the system tray popup menu is being built. You may add custom JMenuItem entries.
| Method | Returns | Description |
|---|---|---|
| getPopupMenu() | javax.swing.JPopupMenu | The Swing popup menu instance. Call .add(item) to append entries. |
OnTooltipDisplayEvent
Not CancellableFired before a tooltip is displayed. You may modify the text.
| Method | Returns | Description |
|---|---|---|
| getRawText() | string | The original unmodified tooltip text. |
| getModifiedText() | string | Current text that will be shown (may already be modified by a higher-priority handler). |
| setModifiedText(text) | void | Override the tooltip text with a new string. |
mm.on('...OnTooltipDisplayEvent', function(e) {
e.setModifiedText(e.getRawText() + ' [from script]');
});
Code Examples
Hello World
// ==UserScript==
var display_name = "Hello World";
var register_name = "hello_world";
var author = "Developer";
var version = "1.0.0";
var description = "Minimal working script.";
var available_version = "*";
// ==/UserScript==
var ctx = mm.getContext();
mm.on('io.github.samera2022.samacros.api.event.events.OnAppLaunchedEvent', function(e) {
mm.log('saMacros ' + e.getAppVersion() + ' on ' + e.getRuntimeEnv());
ctx.showToast('Hello World', 'Script loaded!');
});
mm.on('io.github.samera2022.samacros.api.event.events.AfterRecordStopEvent', function(e) {
mm.log('Recorded ' + e.getTotalActions() + ' actions.');
});
Input Filter โ Drop Keyboard Events
// ==UserScript==
var display_name = "Keyboard Filter";
var register_name = "keyboard_filter";
var author = "Developer";
var version = "1.0.0";
var description = "Prevents keyboard events from being recorded.";
var available_version = "*";
// ==/UserScript==
mm.on('io.github.samera2022.samacros.api.event.events.OnInputCapturedEvent', function(e) {
var t = e.getInputType();
if (t === 10 || t === 11) { // KEY_PRESS or KEY_RELEASE
e.setCancelled(true);
mm.log('Filtered keyboard event (keyCode=' + e.getKeyCode() + ')');
}
});
Pixel Guard โ Abort Playback on Colour Mismatch
// ==UserScript==
var display_name = "Pixel Guard";
var register_name = "pixel_guard";
var author = "Developer";
var version = "1.0.0";
var description = "Stops playback when an expected pixel colour is not present.";
var available_version = "*";
// ==/UserScript==
var ctx = mm.getContext();
// Expect a green button (#00cc00) at (200, 300) with ยฑ30 tolerance
var CHECK_X = 200, CHECK_Y = 300;
var TARGET_R = 0, TARGET_G = 204, TARGET_B = 0, TOLE = 30;
function close(a, b) { return Math.abs(a - b) <= TOLE; }
mm.on('io.github.samera2022.samacros.api.event.events.BeforeStepExecuteEvent', function(e) {
var c = ctx.getPixelColor(CHECK_X, CHECK_Y);
if (!c) return;
if (!close(c.getRed(), TARGET_R) || !close(c.getGreen(), TARGET_G) || !close(c.getBlue(), TARGET_B)) {
mm.log('Pixel guard triggered at step ' + e.getStepIndex() + '. Cancelling.');
e.setCancelled(true);
}
});
Progress Notifications
// ==UserScript==
var display_name = "Progress Notifier";
var register_name = "progress_notifier";
var author = "Developer";
var version = "1.0.0";
var description = "Toast notifications every 25% of loops.";
var available_version = "*";
// ==/UserScript==
var ctx = mm.getContext();
var totalLoops = 0;
mm.on('io.github.samera2022.samacros.api.event.events.BeforePlaybackStartEvent', function(e) {
totalLoops = e.getRepeatCount();
ctx.showToast('Playback Started',
e.getMacroData().size() + ' actions x ' + totalLoops + ' loops'); // Java List: use .size(), not .length
});
mm.on('io.github.samera2022.samacros.api.event.events.OnLoopCompleteEvent', function(e) {
if (totalLoops <= 0) return;
var pct = Math.round((e.getCompletedIterations() / totalLoops) * 100);
if (pct % 25 === 0) {
ctx.showToast('Progress',
pct + '% done (' + e.getCompletedIterations() + '/' + totalLoops + ')');
}
});
mm.on('io.github.samera2022.samacros.api.event.events.OnPlaybackAbortedEvent', function(e) {
ctx.showToast('Aborted',
'Stopped at step ' + e.getAtStepIndex() + ': ' + e.getAbortReason());
});