Skip to content

Macro File Format (.mmc)

Samera2022 edited this page Jan 30, 2026 · 1 revision

Macro File Format (.mmc)

Relevant source files

Purpose and Scope

The .mmc (Mouse Macro) file format is a CSV-based plain text format for persisting recorded mouse and keyboard actions. Files store sequences of input events captured during macro recording, enabling playback across application sessions.

Related Documentation:


File Format Overview

The .mmc file format is a UTF-8 encoded CSV text file where each line represents a single MouseAction. Fields are comma-separated without header rows or comments.

Basic Structure

x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode
x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode
...

File Properties:

Property Value Implementation
Extension .mmc FileConsts.MMC_FILTER
Encoding UTF-8 StandardCharsets.UTF_8
Line Separator Platform-default PrintWriter.println()
Field Separator Comma (,) String concatenation with +
Header None N/A
Comments Not supported N/A

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L160-L163, src/io/github/samera2022/mouse_macros/constant/FileConsts.java L6-L9


Field Definitions

The current format (Version 4) contains 8 fields per action. Each field is serialized as an integer or long value.

Field Index Field Name Data Type Description Valid Values
0 x int X coordinate in virtual screen space Integer
1 y int Y coordinate in virtual screen space Integer
2 type int Action type identifier 1=press, 2=release, 3=wheel, 10=keyPress, 11=keyRelease
3 button int Mouse button identifier 1=left, 2=middle, 3=right, 0=N/A
4 delay long Milliseconds since previous action Non-negative long
5 wheelAmount int Scroll wheel rotation amount Positive=down, Negative=up, 0=N/A
6 keyCode int JNativeHook key code Native key code or 0
7 awtKeyCode int AWT key code for playback AWT KeyEvent code or 0

Coordinate System

The x and y fields represent coordinates in normalized virtual screen space relative to the virtual origin (top-left of the leftmost monitor). During recording, coordinates are normalized via ScreenUtil.normalizeToVirtualOrigin(). During playback, MouseAction.perform() denormalizes them using ScreenUtil.denormalizeFromVirtualOrigin().

Sources: src/io/github/samera2022/mouse_macros/action/MouseAction.java L35-L67


Action Type Enumeration

The type field determines execution in MouseAction.perform():

Action Type Execution Flow

flowchart TD

Perform["MouseAction.perform()"]
Check1["type == 1?"]
Check2["type == 2?"]
Check3["type == 3?"]
Check10["type == 10?"]
Check11["type == 11?"]
Press["robot.mousePress(button)"]
Release["robot.mouseRelease(button)"]
Wheel["robot.mouseWheel(wheelAmount)"]
KeyPress["robot.keyPress(awtKeyCode/keyCode)"]
KeyRelease["robot.keyRelease(awtKeyCode/keyCode)"]

Perform --> Check1
Check1 --> Press
Check1 --> Check2
Check2 --> Release
Check2 --> Check3
Check3 --> Wheel
Check3 --> Check10
Check10 --> KeyPress
Check10 --> Check11
Check11 --> KeyRelease
Loading

Field Usage by Type:

Type Fields Used Fields Ignored
1 (press) x, y, button, delay wheelAmount, keyCode, awtKeyCode
2 (release) x, y, button, delay wheelAmount, keyCode, awtKeyCode
3 (wheel) x, y, wheelAmount, delay button, keyCode, awtKeyCode
10 (keyPress) keyCode, awtKeyCode, delay x, y, button, wheelAmount
11 (keyRelease) keyCode, awtKeyCode, delay x, y, button, wheelAmount

Sources: src/io/github/samera2022/mouse_macros/action/MouseAction.java L35-L67


Format Evolution and Backward Compatibility

The .mmc format has evolved through four versions. The loadFromFile() method implements backward compatibility by detecting field count via arr.length after line.split(",").

Format Version Evolution

flowchart TD

V1["Version 1<br>5 fields<br>arr.length == 5"]
V2["Version 2<br>6 fields<br>arr.length == 6"]
V3["Version 3<br>7 fields<br>arr.length == 7"]
V4["Version 4<br>8 fields<br>arr.length == 8"]

V1 --> V2
V2 --> V3
V3 --> V4
Loading

Version History

Version Field Count Format Features Parser Lines
1 5 x,y,type,button,delay Mouse press/release 238-244
2 6 x,y,type,button,delay,wheelAmount + Mouse wheel (type=3) 230-237
3 7 x,y,type,button,delay,wheelAmount,keyCode + Keyboard events (type=10/11) 221-229
4 8 x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode + AWT key code for reliability 211-220

Default Values for Missing Fields:

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L211-L244


File Loading Algorithm

The loadFromFile() method in MacroManager implements a lenient parser with automatic version detection:

Loading Flow in MacroManager.loadFromFile()

flowchart TD

Start["BufferedReader.readLine()"]
Split["String[] arr = line.split(',')"]
Count["arr.length?"]
V8["arr.length == 8<br>new MouseAction(8 params)"]
V7["arr.length == 7<br>new MouseAction(..., 0)"]
V6["arr.length == 6<br>new MouseAction(..., 0, 0)"]
V5["arr.length == 5<br>new MouseAction(..., 0, 0, 0)"]
Error["catch Exception<br>log line error<br>continue"]
Add["actions.add(mouseAction)"]

Start --> Split
Split --> Count
Count --> V8
Count --> V7
Count --> V6
Count --> V5
Count --> Error
V8 --> Add
V7 --> Add
V6 --> Add
V5 --> Add
Loading

Parsing Implementation

The parser reads files line-by-line using BufferedReader and constructs MouseAction objects:

Field Parsing Logic:

Field Count Constructor Call Missing Fields Defaulted
8 new MouseAction(x, y, type, button, delay, wheelAmount, keyCode, awtKeyCode) None
7 new MouseAction(x, y, type, button, delay, wheelAmount, keyCode, 0) awtKeyCode=0
6 new MouseAction(x, y, type, button, delay, wheelAmount, 0, 0) keyCode=0, awtKeyCode=0
5 new MouseAction(x, y, type, button, delay, 0, 0, 0) wheelAmount=0, keyCode=0, awtKeyCode=0

Error Handling Per Line:

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L200-L253


File Save Operation

The saveToFile() method always writes the current 8-field format:

Save Operation Flow

sequenceDiagram
  participant Component parent
  participant MacroManager.saveToFile()
  participant JFileChooser
  participant CacheManager
  participant PrintWriter

  Component parent->>MacroManager.saveToFile(): "saveToFile(parent)"
  MacroManager.saveToFile()->>MacroManager.saveToFile(): "new JFileChooser()"
  MacroManager.saveToFile()->>MacroManager.saveToFile(): "setFileFilter(FileConsts.MMC_FILTER)"
  MacroManager.saveToFile()->>MacroManager.saveToFile(): "setCurrentDirectory(cached or default path)"
  MacroManager.saveToFile()->>JFileChooser: "showSaveDialog(parent)"
  JFileChooser-->>MacroManager.saveToFile(): "APPROVE_OPTION"
  loop ["!selectedFile.getName().endsW-
    MacroManager.saveToFile()->>MacroManager.saveToFile(): "selectedFile = new File(path + '.mmc')"
    MacroManager.saveToFile()->>CacheManager: "cache.lastSaveDirectory = parent path"
    MacroManager.saveToFile()->>CacheManager: "CacheManager.saveCache()"
    MacroManager.saveToFile()->>PrintWriter: "new PrintWriter(file, UTF-8)"
    MacroManager.saveToFile()->>PrintWriter: "println(x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode)"
  end
  MacroManager.saveToFile()->>PrintWriter: "close()"
  MacroManager.saveToFile()->>MacroManager.saveToFile(): "log('macro_saved')"
Loading

Save Implementation Details

Extension Handling:

// Automatic .mmc extension appending [lines 151-152]
if (!selectedFile.getName().toLowerCase().endsWith(".mmc"))
    selectedFile = new File(selectedFile.getAbsolutePath() + ".mmc");

Directory Caching:

Character Encoding:

Output Format:

// [line 162]
out.println(a.x + "," + a.y + "," + a.type + "," + a.button + "," + a.delay + "," + a.wheelAmount + "," + a.keyCode + "," + a.awtKeyCode);

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L129-L168


Example Files

Example 1: Mouse Click Sequence

A simple macro that moves to coordinates (500, 300), presses the left button, waits 100ms, and releases:

500,300,1,1,0,0,0,0
500,300,2,1,100,0,0,0

Field Breakdown:

  • Line 1: Move to (500, 300), type=1 (press), button=1 (left), no delay, other fields=0
  • Line 2: At (500, 300), type=2 (release), button=1 (left), 100ms delay, other fields=0

Example 2: Mouse Wheel Scroll

A macro that scrolls up at position (800, 600) with a delay of 50ms:

800,600,3,0,50,5,0,0

Field Breakdown:

  • x=800, y=600
  • type=3 (wheel event)
  • button=0 (not used for wheel)
  • delay=50ms
  • wheelAmount=5 (scroll up by 5 units)
  • keyCode=0, awtKeyCode=0 (not used)

Example 3: Keyboard Input

A macro that presses and releases the 'A' key (native keyCode=30, awtKeyCode=65):

0,0,10,0,0,0,30,65
0,0,11,0,50,0,30,65

Field Breakdown:

  • Line 1: type=10 (keyPress), no delay, keyCode=30 (JNativeHook), awtKeyCode=65 (AWT)
  • Line 2: type=11 (keyRelease), 50ms delay, same key codes

Example 4: Legacy Format (5 fields)

An old macro file from version 0.0.1:

100,200,1,1,0
100,200,2,1,50

This file loads successfully with wheelAmount, keyCode, and awtKeyCode defaulting to 0.

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L137, src/io/github/samera2022/mouse_macros/action/MouseAction.java L8-L34


File Location and Management

Directory Resolution Strategy

.mmc files can be saved to any directory. The application maintains separate save/load directory history via CacheManager:

Directory Resolution Logic

flowchart TD

SaveOp["saveToFile() / loadFromFile()"]
Check["config.enableDefaultStorage?"]
DefaultPath["config.defaultMmcStoragePath"]
CachedSave["cache.lastSaveDirectory"]
CachedLoad["cache.lastLoadDirectory"]
SetDir["chooser.setCurrentDirectory()"]
Dialog["JFileChooser dialog"]
UpdateCache["Update cache.lastSave/LoadDirectory"]
Persist["CacheManager.saveCache()"]

SaveOp --> Check
Check --> DefaultPath
Check --> CachedSave
Check --> CachedLoad
DefaultPath --> SetDir
CachedSave --> SetDir
CachedLoad --> SetDir
SetDir --> Dialog
Dialog --> UpdateCache
UpdateCache --> Persist
Loading

Implementation Details:

Operation Directory Source (Priority Order)
Save 1. config.defaultMmcStoragePath (if enableDefaultStorage=true)2. cache.lastSaveDirectory
Load 1. config.defaultMmcStoragePath (if enableDefaultStorage=true)2. cache.lastLoadDirectory

Directory Synchronization:

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L131-L199


Error Handling

Load-Time Error Recovery

The loadFromFile() method implements graceful error handling:

Error Categories:

Error Type Handling Implementation
Per-line parse error Log line number + exception, skip line, continue catch (Exception ex) in inner loop [lines 246-248](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 246-248)
Invalid field count Silently skip line No else clause for unrecognized arr.length
File I/O error Log error, abort load, clear actions catch (Exception ex) in outer scope [lines 251-253](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 251-253)

Localized Error Messages:

// Per-line error [line 247]
log(Localizer.get("log.macro_loading_line_error") + lineNum + ": " + ex.getMessage());

// File-level error [line 252]
log(Localizer.get("log.macro_loading_failed") + ex.getMessage());

Save-Time Error Handling

The saveToFile() method catches all exceptions during file writing:

Error Response:

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L165-L253


Implementation Details

File Chooser Configuration

The JFileChooser uses a file filter defined in FileConsts:

// FileConsts.java
public static final FileNameExtensionFilter MMC_FILTER = 
    new FileNameExtensionFilter("Mouse Macro Files (*.mmc)", "mmc");

Application: chooser.setFileFilter(FileConsts.MMC_FILTER) [lines 148, 190](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 148, 190)

In-Memory Action Storage

Actions are stored in a static list in MacroManager:

// MacroManager.java:20
private static final List<MouseAction> actions = new ArrayList<>();

Operations:

Method Action
startRecording() actions.clear() [line 25](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 25)
recordAction(MouseAction) actions.add(action) [line 111](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 111)
loadFromFile() actions.clear() then actions.add() per line [lines 204, 220, 229, 237, 244](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 204, 220, 229, 237, 244)
saveToFile() for (MouseAction a : actions) [line 161](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 161)
play() for (MouseAction action : actions) [line 49](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 49)

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L20-L204


Integration with Application Workflow

Complete Macro Lifecycle

sequenceDiagram
  participant User
  participant MainFrame
  participant MacroManager
  participant List<MouseAction> actions
  participant File System (.mmc)

  note over User,File System (.mmc): Recording Phase
  User->>MainFrame: "Press F2 (Start)"
  MainFrame->>MacroManager: "startRecording()"
  MacroManager->>List<MouseAction> actions: "actions.clear()"
  loop ["User performs actions"]
    User->>MainFrame: "Input event"
    MainFrame->>MacroManager: "recordAction(mouseAction)"
    MacroManager->>List<MouseAction> actions: "actions.add(mouseAction)"
    User->>MainFrame: "Press F3 (Stop)"
    MainFrame->>MacroManager: "stopRecording()"
    note over User,File System (.mmc): Save Phase
    User->>MainFrame: "Click Save"
    MainFrame->>MacroManager: "saveToFile(parent)"
    MacroManager->>MacroManager: "JFileChooser dialog"
    MacroManager->>File System (.mmc): "println(8 CSV fields)"
    note over User,File System (.mmc): Load Phase
    User->>MainFrame: "Click Load"
    MainFrame->>MacroManager: "loadFromFile(parent)"
    MacroManager->>MacroManager: "JFileChooser dialog"
    MacroManager->>List<MouseAction> actions: "actions.clear()"
    MacroManager->>MacroManager: "arr = line.split(',')"
    MacroManager->>MacroManager: "new MouseAction(...)"
    MacroManager->>List<MouseAction> actions: "actions.add(mouseAction)"
    note over User,File System (.mmc): Playback Phase
    User->>MainFrame: "Press F4 (Play)"
    MainFrame->>MacroManager: "play()"
    MacroManager->>MacroManager: "action.perform()"
  end
Loading

Key Integration Points:

  • Recording: GlobalMouseListenerMacroManager.recordAction()actions.add()
  • Save: actions list → PrintWriter.println().mmc file
  • Load: .mmc file → String.split(",")new MouseAction()actions.add()
  • Playback: actions list → MouseAction.perform()Robot execution

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L24-L254


Summary

The .mmc file format is a simple, extensible CSV-based specification with the following characteristics:

Property Value
Format CSV (comma-separated values)
Encoding UTF-8
Current Version Version 4 (8 fields)
Backward Compatibility Versions 1-4 (5-8 fields)
Extension .mmc
Line Format x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode
Supported Actions Mouse press/release, mouse wheel, keyboard press/release
Error Handling Graceful line-level recovery during load

The format's evolution demonstrates a commitment to backward compatibility while supporting expanded functionality (mouse → wheel → keyboard input). The simple CSV structure enables manual editing for advanced users while remaining robust for automated generation.

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L107-L200, src/io/github/samera2022/mouse_macros/action/MouseAction.java L8-L34, src/io/github/samera2022/mouse_macros/constant/FileConsts.java L6-L9

Clone this wiki locally