-
Notifications
You must be signed in to change notification settings - Fork 0
Macro File Format (.mmc)
Relevant source files
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:
- Runtime recording/playback: MacroManager
- In-memory action representation: MouseAction
- Event capture: Global Input Capture
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.
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
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 |
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
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
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
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
| 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:
- Version 1 → 4:
wheelAmount=0, keyCode=0, awtKeyCode=0[line 244](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 244) - Version 2 → 4:
keyCode=0, awtKeyCode=0[line 237](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 237) - Version 3 → 4:
awtKeyCode=0[line 229](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 229)
Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L211-L244
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
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:
- Parse exceptions: Caught in try-catch, logged with line number, line skipped [lines 246-248](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 246-248)
- Invalid field count: Silently ignored, no error logged
- File I/O errors: Caught at method level, logged and abort [lines 251-253](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 251-253)
Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L200-L253
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')"
Extension Handling:
// Automatic .mmc extension appending [lines 151-152]
if (!selectedFile.getName().toLowerCase().endsWith(".mmc"))
selectedFile = new File(selectedFile.getAbsolutePath() + ".mmc");
Directory Caching:
-
CacheManager.cache.lastSaveDirectoryupdated to parent directory [line 153](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 153) - If
lastLoadDirectoryis empty, synced tolastSaveDirectory[lines 156-159](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 156-159) - Cache persisted via
CacheManager.saveCache()[lines 154, 158](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 154, 158)
Character Encoding:
- Save:
new PrintWriter(selectedFile, StandardCharsets.UTF_8)[line 160](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 160) - Load:
new InputStreamReader(new FileInputStream(selectedFile), StandardCharsets.UTF_8)[lines 201-203](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 201-203)
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
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
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)
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
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
.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
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:
- After save: If
lastLoadDirectoryis empty, set tolastSaveDirectory[lines 156-159](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 156-159) - After load: If
lastSaveDirectoryis empty, set tolastLoadDirectory[lines 196-199](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 196-199) - Prevents empty directory on first operation
Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L131-L199
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());
The saveToFile() method catches all exceptions during file writing:
Error Response:
- Log:
Localizer.get("log.macro_saving_failed") + ex.getMessage()[line 166](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 166) - File may be partially written (no rollback mechanism)
- Possible causes: Permission denied, disk full, I/O error
Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L165-L253
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)
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
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
Key Integration Points:
-
Recording:
GlobalMouseListener→MacroManager.recordAction()→actions.add() -
Save:
actionslist →PrintWriter.println()→.mmcfile -
Load:
.mmcfile →String.split(",")→new MouseAction()→actions.add() -
Playback:
actionslist →MouseAction.perform()→Robotexecution
Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java L24-L254
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