saMacros Icon
saMacros
๐Ÿ“–

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.

GraalVM JS Runtime 15 Event Types 9 Context Sub-APIs Sandboxed by Default

Entry Point

Global mm object injected at script load time.

Engine

GraalVM Polyglot JavaScript โ€” ES2020 compatible.

Package Base

io.github.samera2022.samacros.api

โ„น Java Interop Note: GraalVM exposes Java objects directly to JavaScript. Java 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_namestringYesHuman-readable name shown in Script Manager UI.
register_namestringYesUnique machine identifier (lowercase_snake_case). Used as the dependency key and internal reference.
authorstringYesAuthor name. Used for author-level native-access whitelisting.
versionstringYesScript version โ€” semantic versioning (MAJOR.MINOR.PATCH) recommended.
descriptionstringYesShort description shown in Script Manager.
available_versionstringNoCompatible saMacros version(s). Supports *, exact (2.0.0), wildcard (2.*), range (2.0.0~2.1.*).
hard_dependenciesstring[]NoArray of register_names that must be enabled. Script will not load if any are missing.
soft_dependenciesstring[]NoOptional script dependencies. Script still loads if these are missing.
requireNativeAccessbooleanNoRequest unrestricted JVM/filesystem access. Script is disabled on first load until user approves.
requireNativeAccessDescriptionstringNoExplanation 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:

  1. LOWEST โ†’ first
  2. LOW
  3. NORMAL (default for mm.on)
  4. HIGH
  5. HIGHEST
  6. 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()longUnix 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.

MethodReturnsDescription
getScale()double[]DPI scale factors for primary screen as [scaleX, scaleY]. E.g. [1.5, 1.5] on 150% DPI.
isSystemDarkMode()booleanOS dark mode flag. Reliable on Windows 10+ only; returns false on other platforms.
getSystemLanguage()stringSystem locale code, e.g. "en_us", "zh_cn".
getOSName()stringValue of System.getProperty("os.name"), e.g. "Windows 11", "Linux".
getJavaVersion()stringValue 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.

MethodReturnsDescription
getWidth()intWidth of the primary screen in pixels.
getHeight()intHeight of the primary screen in pixels.
getScreenCount()intNumber of connected monitors.
getVirtualOrigin()java.awt.PointTop-left corner of the combined virtual screen (may be negative in multi-monitor setups).
normalizeToVirtualOrigin(x,y)java.awt.PointConvert primary-screen coordinates to virtual-screen (stored) coordinates.
denormalizeFromVirtualOrigin(x,y)java.awt.PointConvert 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.

MethodReturnsDescription
isRecording()booleantrue while the user is actively recording.
isPlaying()booleantrue while playback is running (including paused state).
isPaused()booleantrue when playback is paused. Implies isPlaying() === true.
getActionsCount()intNumber 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.

MethodReturnsDescription
getPosition()java.awt.Point | nullCurrent 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.

MethodReturnsDescription
get(key)stringTranslated string for key in the current language. Returns key itself if not found.
hasKey(key)booleantrue if a translation exists for the given key.
getCurrentLanguage()stringActive 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().

MethodReturnsDescription
getBoolean(key)booleanRead a boolean setting value.
getInt(key)intRead an integer setting value.
getDouble(key)doubleRead a floating-point setting value.
getString(key)stringRead a string setting value.
getKeyMap()Map<string,string>All configuration pairs as a key-to-string map.

Known Configuration Keys

KeyTypeDescription
followSystemSettingsbooleanInherit system language and dark-mode when true.
langstringUI language code when not following system ("en_us", "zh_cn", โ€ฆ).
enableDarkModebooleanDark theme flag when not following system.
defaultStoragePathstringAbsolute path to the directory pre-filled in Save/Load dialogs. If the path does not exist, the dialog opens at the system default instead.
repeatTimesintPlayback loop count (0 = infinite).
quickModebooleanSkip inter-action delays during playback.

IMouseAction / MouseAction

The concrete MouseAction is what you receive in event callbacks and pass to ctx.simulate().

Public Fields

FieldTypeDescription
xintScreen X coordinate (virtual-origin-normalised).
yintScreen Y coordinate (virtual-origin-normalised).
typeint1=mouse press   2=mouse release   3=mouse wheel   10=key press   11=key release
buttonintMouse button: 1=left, 2=middle, 3=right. Ignored for keyboard events.
delaylongWait (ms) before this action executes during playback.
wheelAmountintScroll notches (positive=down, negative=up). Zero for non-wheel events.
keyCodeintNative OS key code for keyboard events.
awtKeyCodeintAWT KeyEvent virtual key code for Robot playback. Takes precedence over keyCode when non-zero.
metadataMap<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 Cancellable

Fired after all scripts have loaded and the application has fully initialised. Typically the first event your script receives.

Method Returns Description
getAppVersion()stringsaMacros version string, e.g. "2.0.0".
getRuntimeEnv()stringRuntime identifier, e.g. "GraalVM", "JRE 21".
mm.on('...OnAppLaunchedEvent', function(e) {
  mm.log('saMacros ' + e.getAppVersion() + ' on ' + e.getRuntimeEnv());
});

OnConfigChangedEvent

Not Cancellable

Fired whenever a settings value changes (user saves the Settings dialog).

Method Returns Description
getConfigKey()stringConfig key that changed.
getOldValue()ObjectPrevious value (may be null).
getNewValue()ObjectNew value.
mm.on('...OnConfigChangedEvent', function(e) {
  mm.log(e.getConfigKey() + ' changed: ' + e.getOldValue() + ' -> ' + e.getNewValue());
});

โบ Recording Events

BeforeRecordStartEvent

Cancellable

Fired just before the recording session begins. Cancel to prevent recording from starting.

Method Returns Description
getStartTime()longUnix timestamp (ms) when recording was requested.
getInitialMousePos()java.awt.PointMouse cursor position at the start of recording.

OnInputCapturedEvent

Cancellable

Fired for every mouse/keyboard input captured during recording. Cancel to discard this particular input.

Method Returns Description
getInputType()int1=MousePress, 2=MouseRelease, 3=MouseWheel, 10=KeyPress, 11=KeyRelease.
getKeyCode()intKey or button code.
getX()intMouse X at time of capture.
getY()intMouse Y at time of capture.
getDelay()longMilliseconds 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

Cancellable

Fired after an input passes capture and is about to be appended to the macro action list. Cancel to remove it.

Method Returns Description
getAction()IMouseActionThe action being added.
getCurrentIndex()intZero-based list index where the action will be inserted.

AfterRecordStopEvent

Not Cancellable

Fired 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()intTotal number of recorded actions.
mm.on('...AfterRecordStopEvent', function(e) {
  mm.log('Recorded ' + e.getTotalActions() + ' actions.');
});

โ–ถ Playback Events

BeforePlaybackStartEvent

Cancellable

Fired 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()intLoop count. -1 for infinite.

BeforeStepExecuteEvent

Cancellable

Fired before each individual action during playback. Cancel to skip that step (recorded as SKIPPED in AfterStepExecuteEvent).

Method Returns Description
getStepIndex()intZero-based index in the action list.
getAction()IMouseActionThe action about to execute.
isLastStep()booleantrue when this is the final action in the current loop iteration.

AfterStepExecuteEvent

Not Cancellable

Fired after each action completes.

Method Returns Description
getStepIndex()intZero-based step index.
getExecutionStatus()string"SUCCESS", "FAILED", or "SKIPPED".

OnLoopCompleteEvent

Not Cancellable

Fired after all actions in one loop iteration have executed.

Method Returns Description
getCompletedIterations()intNumber of iterations completed so far.
getRemainingIterations()intRemaining iterations. -1 for infinite loops.
mm.on('...OnLoopCompleteEvent', function(e) {
  mm.log('Loop ' + e.getCompletedIterations() + ' done, ' + e.getRemainingIterations() + ' left.');
});

OnPlaybackAbortedEvent

Not Cancellable

Fired when playback is interrupted before completing all loops.

Method Returns Description
getAbortReason()stringHuman-readable reason, e.g. "INTERRUPTED", "EXCEPTION: ...".
getAtStepIndex()intZero-based step index where the abort occurred.

๐Ÿ’พ File I/O Events

OnMacroLoadEvent

Not Cancellable

Fired after a .mmc file has been read from disk and parsed into the action list.

Method Returns Description
getFile()java.io.FileThe loaded file. Use .getAbsolutePath() for the path string.
getRawContent()stringRaw UTF-8 file content.

OnMacroSaveEvent

Not Cancellable

Fired after the macro action list has been serialised and written to disk.

Method Returns Description
getFile()java.io.FileThe target file that was written.
getFormattedContent()stringThe CSV content written to the file.

๐Ÿ–ผ UI Events

OnMenuInitEvent

Not Cancellable

Fired when the system tray popup menu is being built. You may add custom JMenuItem entries.

Method Returns Description
getPopupMenu()javax.swing.JPopupMenuThe Swing popup menu instance. Call .add(item) to append entries.

OnTooltipDisplayEvent

Not Cancellable

Fired before a tooltip is displayed. You may modify the text.

Method Returns Description
getRawText()stringThe original unmodified tooltip text.
getModifiedText()stringCurrent text that will be shown (may already be modified by a higher-priority handler).
setModifiedText(text)voidOverride 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());
});