This document provides a technical overview of the Inputs Emulator project, a macOS command-line application designed to monitor, record, save, load, and simulate keyboard and mouse events.
The application leverages macOS Core Graphics and Application Services frameworks for event tapping and simulation.
The application follows a modular design:
- Main Application (
main.cpp): Handles user interaction via a command-line menu, orchestrates the overall workflow, manages the lifecycle of theInputMonitor, and initiates saving, loading, and simulation processes. - Input Monitoring (
InputMonitor): Responsible for capturing system-wide keyboard and mouse events using a Core Graphics Event Tap (CGEventTapCreate). It runs the event tap on a dedicatedCFRunLoopmanaged within a separatestd::thread. - Event Logging (
InputLogger): Stores captured events in memory (std::vector<std::unique_ptr<Event>>). Provides functionalities for accessing, sorting, clearing, and managing these events. It collaborates withFileLoggerfor persistence. - File Persistence (
FileLogger): Handles serialization and deserialization of event data to/from files. Currently uses a JSON format for storing event sequences. - Event Data Structures (
Event,KeyEvent,MouseEvent): Define the structure for storing individual input events, including timestamps, event types, and event-specific data (key codes, mouse coordinates, button states, scroll deltas). - Simulation (
Simulation,Keyboard,Mouse): Takes a sequence ofEventobjects and simulates them programmatically. TheSimulationclass manages the timing and dispatching of events to theKeyboardandMouseclasses, which encapsulate the low-level logic for creating (CGEventCreate...) and posting (CGEventPost) synthetic events. - Utilities (
Util): Provides helper functions, such as time calculations and cross-platform sleep functionality.
- Monitoring:
InputMonitorcreates an event tap -> macOS sends events toInputMonitor::eventCallback. - Callback Processing:
eventCallbackdetermines event type -> CreatesKeyEventorMouseEvent-> Logs the event viaInputLogger. - Storage:
InputLoggeradds thestd::unique_ptr<Event>to its internal vector. - Saving:
maintriggers save ->InputLoggerprovides event data ->FileLoggerserializes events to a JSON file. - Loading:
maintriggers load ->FileLoggerdeserializes events from JSON file ->InputLoggerstores the loaded events (potentially clearing existing ones). - Simulation:
maintriggers simulation ->Simulationclass receives event data fromInputLogger->Simulationcalculates delays and calls methods onKeyboard/Mouse->Keyboard/Mousecreate and postCGEvents.
- Event Tapping: Uses
CGEventTapCreateto install an event tap listening for key down/up, mouse down/up/move, and scroll wheel events. - Threading: The
CFRunLooprequired by the event tap is run on a dedicatedstd::thread(monitorThread) created in thestart()method. This prevents blocking the main application thread. - Callback (
eventCallback): A static C-style function passed toCGEventTapCreate. It receives event details, reconstructs theInputMonitorinstance pointer (refcon), creates the appropriateEventsubclass object, and logs it using the associatedInputLogger. - State Management: Uses
std::atomic<bool> isMonitoringto track the running state. This is crucial for thread safety between the main thread and the monitor thread. - Stopping Mechanism: The
stop()method signals theCFRunLoopto stop (CFRunLoopStop) using an atomic flag (stopRequested) andCFRunLoopPerformBlock. It then waits for themonitorThreadto join, ensuring resources are cleaned up properly.compare_exchange_strongis used onstopRequestedto prevent multiple stop attempts. - Escape Key Handling: The
eventCallbacknow checks for the Escape key (kVK_Escape) press and callsmonitor->stop()directly, simplifying the logic inmain.cpp. - Throttling: Basic mouse move event throttling is implemented in
eventCallbackto reduce the number of logged move events based on distance and time intervals.
- Storage: Employs
std::vector<std::unique_ptr<Event>> eventsto store event objects polymorphically. - Management: Provides methods like
logKeyEvent,logMouseEvent,getAllEvents,clear,sortAllEvents(usingstd::sortwith a custom timestamp comparator). - Persistence Interface: Uses
FileLoggerfor saving (saveAllEvents) and loading (logAllEventsFromFile, which takes ownership of the loaded events vector).
- Format: Serializes the
eventsvector into a JSON format. Each event object is converted into a JSON object containing its type and relevant fields (timestamp, keyCode, isKeyDown, eventState, button, position, scrollUnit). - Library: Uses the
nlohmann/jsonlibrary (included viathird_party) for JSON parsing and serialization. - I/O: Uses
std::ofstreamfor writing andstd::ifstreamfor reading.
Event(Base): Abstract base class defining common properties liketimestampandeventType, and virtual methods likegetEventTypeString,toJSON.KeyEvent: Derived fromEvent. StoreskeyCode(CGKeyCode),isKeyDown(bool), and potentiallykeyString.MouseEvent: Derived fromEvent. StoreseventState(enum: PRESSED, RELEASED, MOVED, SCROLLED),button(int, relevant for clicks),position(CGPoint),scrollUnit(CGVector, relevant for scrolls).
Simulation::runSimulation: Iterates through the provided event vector. Calculates the delay between consecutive events based on their timestamps and usesstd::this_thread::sleep_forto pause execution, simulating the original timing.- Event Dispatch: Calls appropriate methods on
KeyboardorMouseinstances based on the event type. Keyboard/Mouse: These classes contain methods (keyDown,keyUp,mousePress,mouseRelease,moveTo,scroll, etc.) that:- Create the corresponding
CGEventusing functions likeCGEventCreateKeyboardEvent,CGEventCreateMouseEvent,CGEventCreateScrollWheelEvent. - Important: Include
NULLchecks after event creation calls to handle potential system failures gracefully. - Post the created event to the system event stream using
CGEventPost(kCGHIDEventTap, event). Event taps likekCGHIDEventTapallow posting low-level events. - Manage event resources (e.g.,
CFRelease(event)).
- Create the corresponding
- Uses
g++as the compiler. - Sets
CXXFLAGSto-std=c++14 -Walland includes header directories (-Iinclude,-Ithird_party). - Links against the required macOS framework:
-framework ApplicationServices. - Uses
findto locate source files (.cpp) recursively. - Defines separate directories for object files (
obj/) and the final executable (debug/or potentiallyrelease/). - Includes targets for
all,clean, anddirectories.
- Event Tap Threading: Using a separate thread and
CFRunLoopfor the event tap is essential for responsiveness and preventing blocking the main UI thread. - Atomic Flags:
std::atomic<bool>is used forisMonitoringandstopRequestedto ensure thread-safe communication between the main thread and the monitor thread. std::unique_ptr: Used extensively for managing the lifetime ofEventobjects in theInputLoggerand during file loading, preventing memory leaks.- Event Simulation API:
CGEventPostwithkCGHIDEventTapprovides a robust way to inject low-level input events. - Error Handling: Added
NULLchecks afterCGEventCreate...calls to prevent crashes if the system fails to create an event. Basic exception handling is present in file I/O and simulation loops. - JSON Persistence: JSON provides a human-readable and relatively easy-to-parse format for saving/loading events.
- More robust error handling throughout the application.
- Support for simulating modifier key combinations more explicitly.
- Refined simulation timing (potential inaccuracies in
sleep_for). - Support for different file formats (e.g., binary for efficiency).
- More sophisticated event filtering or transformation options.