cyberangles blog

How to Capture Mouse Move Events from Electron Main Process (Even When Mouse is Outside the Window)

Electron has revolutionized desktop application development by allowing developers to build cross-platform apps using web technologies (HTML, CSS, and JavaScript). A common requirement in desktop apps is tracking user input, such as mouse movements. While capturing mouse events within an Electron window is straightforward using the renderer process (via standard DOM events like mousemove), capturing events outside the app window or globally requires a different approach.

In this blog, we’ll explore how to capture mouse move events directly from the Electron main process, even when the mouse is outside the application window. We’ll cover the challenges involved, introduce tools to overcome them, and provide a step-by-step implementation guide. This is especially useful for use cases like screen recording tools, accessibility apps, or custom input managers that need to monitor user interactions system-wide.

2025-12

Table of Contents#

  1. Prerequisites
  2. Understanding Electron Processes
  3. Challenges with Global Mouse Events
  4. Solution: Using Native Input Event Libraries
  5. Step-by-Step Implementation
  6. Handling Edge Cases & Cross-Platform Considerations
  7. Security & Performance Best Practices
  8. Conclusion
  9. References

Prerequisites#

Before diving in, ensure you have the following:

  • Basic knowledge of Electron (familiarity with main/renderer processes).
  • Node.js (v14+ recommended) and npm/yarn installed.
  • A code editor (e.g., VS Code).
  • An existing Electron project (or we’ll set one up from scratch).

Understanding Electron Processes#

Electron apps run in two primary processes:

  • Main Process: Manages the app lifecycle, window creation, and system-level interactions. It has access to Node.js APIs and Electron’s core modules (e.g., BrowserWindow, app).
  • Renderer Process: Runs in a Chromium instance and handles the app’s UI (DOM, web APIs). It can communicate with the main process via IPC (Inter-Process Communication).

By default, mouse events like mousemove are only accessible in the renderer process when the mouse is within the app window. To capture events outside the window, we need to tap into system-level input events, which requires native code integration—something the main process can handle with the right tools.

Challenges with Global Mouse Events#

Electron’s built-in APIs do not natively support global mouse event tracking. Here’s why:

  • Renderer Process Limitations: DOM events like mousemove are scoped to the app window. Once the mouse leaves the window, these events stop firing.
  • Main Process Restrictions: The main process lacks direct access to low-level input events. It can query the current mouse position (e.g., via screen.getCursorScreenPoint()), but this only gives a snapshot, not continuous event updates.

To solve this, we need a way to listen to global input events at the operating system level. This requires a native Node.js module that bridges Electron to the OS’s input subsystem.

Solution: Using Native Input Event Libraries#

Native Node.js modules extend Node.js with low-level OS capabilities. For global mouse events, two popular libraries stand out:

1. iohook#

A cross-platform library for listening to global keyboard and mouse events. It supports Windows, macOS, and Linux, and provides real-time event callbacks for mouse movements, clicks, scrolls, etc.—even when the app is minimized or in the background.

2. robotjs#

While robotjs can retrieve the current mouse position (via robotjs.getMousePos()), it does not natively support event listening. It’s better for simulating input rather than capturing it.

For our use case, iohook is the ideal choice, as it focuses on global event listening.

Step-by-Step Implementation#

In this section, we’ll walk through setting up an Electron app and using iohook to capture global mouse move events from the main process.

Step 1: Set Up an Electron Project#

If you don’t already have an Electron project, create one with these commands:

# Initialize a new project
mkdir electron-mouse-tracker && cd electron-mouse-tracker
npm init -y
 
# Install Electron as a dev dependency
npm install electron --save-dev

Update package.json to include Electron start scripts:

{
  "name": "electron-mouse-tracker",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^28.0.0"
  }
}

Step 2: Install iohook and Rebuild for Electron#

iohook is a native module, so it must be rebuilt to work with Electron’s Node.js version (Electron uses a custom Node.js runtime). We’ll use electron-rebuild to handle this.

Install Dependencies:#

# Install iohook
npm install iohook --save
 
# Install electron-rebuild as a dev dependency
npm install electron-rebuild --save-dev

Rebuild iohook for Electron:#

Run electron-rebuild to compile iohook against your Electron version:

# Rebuild native modules
./node_modules/.bin/electron-rebuild

Note: If you encounter errors (e.g., "Module version mismatch"), ensure electron-rebuild targets your installed Electron version. You can specify the version explicitly:

./node_modules/.bin/electron-rebuild --version 28.0.0

Step 3: Capture Mouse Move Events in the Main Process#

Create a main.js file (the main process entry point) and add the following code to listen for global mouse move events using iohook:

const { app, BrowserWindow } = require('electron');
const ioHook = require('iohook');
 
// Keep a global reference to the window object to prevent garbage collection
let mainWindow;
 
function createWindow() {
  // Create the browser window
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // Disable nodeIntegration for security
      contextIsolation: true, // Enable context isolation
      preload: `${__dirname}/preload.js` // Preload script for IPC
    }
  });
 
  // Load an HTML file (we'll create this next)
  mainWindow.loadFile('index.html');
 
  // Open DevTools (optional)
  mainWindow.webContents.openDevTools();
 
  // Emitted when the window is closed
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}
 
// Initialize the app
app.whenReady().then(() => {
  createWindow();
 
  // Set up global mouse move listener with iohook
  ioHook.on('mousemove', (event) => {
    const { x, y } = event;
    console.log(`Global Mouse Position: (${x}, ${y})`);
 
    // Optional: Send coordinates to the renderer process via IPC
    if (mainWindow) {
      mainWindow.webContents.send('mouse-move', { x, y });
    }
  });
 
  // Start listening for events
  ioHook.start();
 
  // Handle app activation (macOS)
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});
 
// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    ioHook.stop(); // Stop listening to events before quitting
    app.quit();
  }
});

Step 4: Create a Renderer UI (Optional)#

To display the mouse coordinates in the app window, create an index.html file and a preload.js script for IPC communication between the main and renderer processes.

index.html:#

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Global Mouse Tracker</title>
  </head>
  <body>
    <h1>Global Mouse Position</h1>
    <p id="mouse-coords">X: --, Y: --</p>
 
    <script src="./renderer.js"></script>
  </body>
</html>

preload.js (IPC Bridge):#

const { ipcRenderer } = require('electron');
 
// Expose a safe API to the renderer process
window.electron = {
  onMouseMove: (callback) => ipcRenderer.on('mouse-move', (event, coords) => callback(coords))
};

renderer.js (Update UI with Coordinates):#

// Listen for mouse move events from the main process
window.electron.onMouseMove(({ x, y }) => {
  const coordsElement = document.getElementById('mouse-coords');
  coordsElement.textContent = `X: ${x}, Y: ${y}`;
});

Step 5: Run the App#

Start the Electron app and test global mouse tracking:

npm start

You’ll see:

  • The app window displaying real-time mouse coordinates.
  • Console logs in the main process with global mouse positions (even when the mouse is outside the window).

Handling Edge Cases & Cross-Platform Considerations#

1. macOS Accessibility Permissions#

On macOS, iohook requires Accessibility permissions to capture global input events. If events aren’t being captured:

  1. Open System Preferences > Security & Privacy > Privacy > Accessibility.
  2. Unlock the settings (click the lock icon).
  3. Check your Electron app in the list of allowed apps.

2. Performance Optimization#

Mouse move events fire hundreds of times per second. To avoid performance lag:

  • Debounce events if you don’t need sub-millisecond precision (e.g., update coordinates every 50ms).
  • Avoid heavy computations in the mousemove callback. Offload work to a separate thread if needed.

3. Cross-Platform Compatibility#

  • Windows: No special permissions required (runs out of the box).
  • Linux: Ensure libx11-dev and libxtst-dev are installed (required for X11 input handling):
    sudo apt-get install libx11-dev libxtst-dev
  • macOS: As noted, accessibility permissions are mandatory.

Security & Performance Best Practices#

1. Native Module Security#

Native modules like iohook run with full system access. Only use trusted libraries, and audit dependencies for vulnerabilities.

2. User Privacy#

Global input tracking is privacy-sensitive. Always inform users why your app needs this capability (e.g., "This app tracks mouse movements to enable screen recording features").

3. Production Hardening#

  • Code Signing: Sign your app to avoid OS security warnings (critical for macOS notarization).
  • Minimize Permissions: Only request necessary OS permissions (e.g., accessibility access on macOS).
  • Update Dependencies: Keep iohook and Electron updated to patch security vulnerabilities.

Conclusion#

Capturing global mouse move events from the Electron main process is achievable with native modules like iohook. By following this guide, you can track mouse movements outside your app window for use cases like accessibility tools, screen recorders, or custom input managers.

Key takeaways:

  • Use iohook for cross-platform global input event listening.
  • Rebuild native modules with electron-rebuild to ensure compatibility with Electron.
  • Handle platform-specific permissions (e.g., macOS Accessibility).
  • Prioritize security and user privacy when capturing global input.

References#