# Macro File Format (.mmc) > **Relevant source files** > * [src/io/github/samera2022/mouse_macros/manager/MacroManager.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/MacroManager.java) ## 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:** * Runtime recording/playback: [MacroManager](MacroManager) * In-memory action representation: [MouseAction](MouseAction-Data-Structure) * Event capture: [Global Input Capture](Global-Input-Capture) --- ## 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/MacroManager.java#L160-L163), [src/io/github/samera2022/mouse_macros/constant/FileConsts.java L6-L9](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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** ```mermaid 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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** ```mermaid flowchart TD V1["Version 1
5 fields
arr.length == 5"] V2["Version 2
6 fields
arr.length == 6"] V3["Version 3
7 fields
arr.length == 7"] V4["Version 4
8 fields
arr.length == 8"] V1 --> V2 V2 --> V3 V3 --> V4 ``` ### Version History | Version | Field Count | Format | Features | Parser Lines | | --- | --- | --- | --- | --- | | 1 | 5 | `x,y,type,button,delay` | Mouse press/release | [238-244](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/238-244) | | 2 | 6 | `x,y,type,button,delay,wheelAmount` | + Mouse wheel (type=3) | [230-237](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/230-237) | | 3 | 7 | `x,y,type,button,delay,wheelAmount,keyCode` | + Keyboard events (type=10/11) | [221-229](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/221-229) | | 4 | 8 | `x,y,type,button,delay,wheelAmount,keyCode,awtKeyCode` | + AWT key code for reliability | [211-220](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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()** ```mermaid flowchart TD Start["BufferedReader.readLine()"] Split["String[] arr = line.split(',')"] Count["arr.length?"] V8["arr.length == 8
new MouseAction(8 params)"] V7["arr.length == 7
new MouseAction(..., 0)"] V6["arr.length == 6
new MouseAction(..., 0, 0)"] V5["arr.length == 5
new MouseAction(..., 0, 0, 0)"] Error["catch Exception
log line error
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 ``` ### 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:** * 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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** ```mermaid 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')" ``` ### 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:** * `CacheManager.cache.lastSaveDirectory` updated to parent directory [line 153](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 153) * If `lastLoadDirectory` is empty, synced to `lastSaveDirectory` [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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/MacroManager.java#L137-L137), [src/io/github/samera2022/mouse_macros/action/MouseAction.java L8-L34](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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** ```mermaid 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 `lastLoadDirectory` is empty, set to `lastSaveDirectory` [lines 156-159](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 156-159) * After load: If `lastSaveDirectory` is empty, set to `lastLoadDirectory` [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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:** * 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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`: ```java // MacroManager.java:20 private static final List 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/MacroManager.java#L20-L204) --- ## Integration with Application Workflow **Complete Macro Lifecycle** ```mermaid sequenceDiagram participant User participant MainFrame participant MacroManager participant List actions participant File System (.mmc) note over User,File System (.mmc): Recording Phase User->>MainFrame: "Press F2 (Start)" MainFrame->>MacroManager: "startRecording()" MacroManager->>List actions: "actions.clear()" loop ["User performs actions"] User->>MainFrame: "Input event" MainFrame->>MacroManager: "recordAction(mouseAction)" MacroManager->>List 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 actions: "actions.clear()" MacroManager->>MacroManager: "arr = line.split(',')" MacroManager->>MacroManager: "new MouseAction(...)" MacroManager->>List 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**: `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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/MacroManager.java#L107-L200), [src/io/github/samera2022/mouse_macros/action/MouseAction.java L8-L34](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/action/MouseAction.java#L8-L34), [src/io/github/samera2022/mouse_macros/constant/FileConsts.java L6-L9](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/constant/FileConsts.java#L6-L9)