npm package: @quantatrisk/node-selection
Description: Text selection monitoring of native Node.js module with N-API across applications
Author: Quantatrisk
Latest version: 1.0.0

# node-selection

[![npm version](https://img.shields.io/npm/v/@quantatrisk/node-selection?style=flat)](https://www.npmjs.org/package/@quantatrisk/node-selection)

**[中文文档](./docs/README_CN.md)**

A native Node.js module with Node-API that allows monitoring text selections across applications using multiple methods.

## Features

- **Cross-application text selection monitoring**
  - Capture selected text content and its screen coordinates
  - Auto-triggers on user selection, or manual API triggers
  - Rich API to control the selection behaviors
- **Input event listeners**
  - Mouse events: `down`/`up`/`wheel`/`move` with buttons information
  - Keyboard events: `keydown`/`keyup` with keys information
  - _No additional hooks required_ – works natively.
- **Multi-method to get selected text** (automatic fallback):
  - For Windows:
    - _UI Automation_ (modern apps)
    - _Accessibility API_ (legacy apps)
  - For macOS:
    - _Accessibility API (AXAPI)_
  - For all platforms:
    - _Clipboard fallback_ (simulated `Ctrl + C`/`⌘ + C` with optimizations when all other methods fail)
- **Text insertion**
  - Insert text at cursor position
  - Streaming insert for LLM token-by-token output
- **Clipboard**
  - Read/write clipboard
- **Compatibility**
  - Node.js `v18+` | Electron `v23+`
  - TypeScript support included.

## Supported Platforms

| Platform | Status |
|----------|--------|
| Windows  | ✅ Fully supported. Windows 7+   |
| macOS    | ✅ Fully supported. macOS 10.14+ |
| Linux    | 🚧 Coming soon                   |

## Installation

```bash
npm install @quantatrisk/node-selection
```

## Demo

```bash
npm run demo
```

## Building

### Use pre-built packages

The npm package ships with pre-built `.node` files in `prebuilds/*` — no rebuilding needed.

### Build from scratch

- Use `npm run rebuild` to build your platform's specific package.
- Use `npm run prebuild` to build packages for all the supported platforms.

#### Python setuptools

When building, if the `ModuleNotFoundError: No module named 'distutils'` error prompt appears, please install the necessary Python library via `pip install setuptools`.

### Electron rebuilding

When using `electron-builder` for packaging, Electron will forcibly rebuild Node packages. In this case, you may need to run `npm install` in `./node_modules/@quantatrisk/node-selection` in advance to ensure the necessary packages are downloaded.

### Avoid Electron rebuilding

When using `electron-forge` for packaging, you can add these values to your `electron-forge` config to avoid rebuilding:

```javascript
rebuildConfig: {
    onlyModules: [],
},
```

## Usage

### JavaScript

```javascript
const SelectionHook = require("@quantatrisk/node-selection");

// Create a new instance
// You can design it as a singleton pattern to avoid resource consumption from multiple instantiations
const selectionHook = new SelectionHook();

// Listen for text selection events
selectionHook.on("text-selection", (data) => {
  console.log("Selected text:", data.text);
  console.log("Program:", data.programName);
  console.log("Position:", data.startTop, data.endBottom);
});

// Start monitoring (with default configuration)
selectionHook.start();

// When you want to get the current selection directly
const currentSelection = selectionHook.getCurrentSelection();
if (currentSelection) {
  console.log("Current selection:", currentSelection.text);
}

// Stop, you can start it again
selectionHook.stop();
// Clean up when done
selectionHook.cleanup();
```

### TypeScript

```typescript
import {
  SelectionHookConstructor,
  SelectionHookInstance,
  SelectionConfig,
  TextSelectionData,
  MouseEventData,
  KeyboardEventData,
} from "@quantatrisk/node-selection";

// Import the constructor
const SelectionHook: SelectionHookConstructor = require("@quantatrisk/node-selection");

// Create instance with type annotation
const hook: SelectionHookInstance = new SelectionHook();

// Configure and start with typed config
const config: SelectionConfig = {
  debug: false,
  enableMouseMoveEvent: false,
  enableClipboard: true,
  selectionPassiveMode: false,
};

hook.start(config);

// Listen for text selection with typed data
hook.on("text-selection", (data: TextSelectionData) => {
  console.log("Selected:", data.text);
  console.log("From:", data.programName);
  console.log("Method:", data.method); // SelectionMethod enum
  console.log("Position level:", data.posLevel); // PositionLevel enum
});

// Listen for mouse events
hook.on("mouse-down", (data: MouseEventData) => {
  console.log(`Mouse down at (${data.x}, ${data.y}), button: ${data.button}`);
});

// Listen for keyboard events
hook.on("key-down", (data: KeyboardEventData) => {
  console.log(`Key: ${data.uniKey}, vkCode: ${data.vkCode}`);
});

// Get current selection with type safety
const selection: TextSelectionData | null = hook.getCurrentSelection();
if (selection) {
  console.log(selection.text);
}

// Streaming insert for LLM output
hook.setInsertEventCallback((event) => {
  if (event.status === "ended") {
    console.log("Insert completed:", event.streamId);
  } else if (event.status === "error") {
    console.error("Insert error:", event.reason);
  }
});

const streamId = hook.beginInsertStream();
if (streamId) {
  hook.pushInsertChunk(streamId, "Hello ");
  hook.pushInsertChunk(streamId, "World!");
  hook.endInsertStream(streamId);
}

// Cleanup
hook.cleanup();
```

See [`examples/selection-demo.js`](https://github.com/Quantatirsk/node-selection/blob/main/examples/selection-demo.js) for detailed usage.

## API Reference

### Constructor

```javascript
const hook = new SelectionHook();
```

### Methods

#### **`start(config?: SelectionConfig): boolean`**

Start monitoring text selections.

Config options (with default values):

```javascript
{
  debug?: false,                 // Enable debug logging
  enableMouseMoveEvent?: false,  // Enable mouse move tracking, can be set in runtime
  enableClipboard?: true,        // Enable clipboard fallback, can be set in runtime
  selectionPassiveMode?: false,  // Enable passive mode, can be set in runtime
  clipboardMode?: SelectionHook.FilterMode.DEFAULT,    // Clipboard mode, can be set in runtime
  clipboardFilterList?: string[]                       // Program list for clipboard mode, can be set in runtime
  globalFilterMode?: SelectionHook.FilterMode.DEFAULT, // Global filter mode, can be set in runtime
  globalFilterList?: string[]                          // Global program list for global filter mode, can be set in runtime
}
```

see [`SelectionHook.FilterMode`](#selectionhookfiltermode) for details

**For _macOS_**:
macOS requires accessibility permissions for the selection-hook to function properly. Please ensure that the user has enabled accessibility permissions before calling start().

- **Node**: use `macIsProcessTrusted` and `macRequestProcessTrust` to check whether the permission is granted.
- **Electron**: use `electron`'s `systemPreferences.isTrustedAccessibilityClient` to check whether the permission is granted.

#### **`stop(): boolean`**

Stop monitoring text selections.

#### **`getCurrentSelection(): TextSelectionData | null`**

Get the current text selection if any exists.

#### **`enableMouseMoveEvent(): boolean`**

Enable mouse move events (high CPU usage). Disabled by default.

#### **`disableMouseMoveEvent(): boolean`**

Disable mouse move events. Disabled by default.

#### **`enableClipboard(): boolean`**

Enable clipboard fallback for text selection. Enabled by default.

#### **`disableClipboard(): boolean`**

Disable clipboard fallback for text selection. Enabled by default.

#### **`setClipboardMode(mode: ClipboardMode, programList?: string[]): boolean`**

Configure how clipboard fallback works with different programs. See `SelectionHook.FilterMode` constants for details.

#### **`setGlobalFilterMode(mode: FilterMode, programList?: string[]): boolean`**

Configure which applications should trigger text selection events. You can include or exclude specific applications from the selection monitoring. See `SelectionHook.FilterMode` constants for details.

#### **`setFineTunedList(listType: FineTunedListType, programList?: string[]): boolean`**

_Windows Only_

Configure fine-tuned lists for specific application behaviors. This allows you to customize how the selection hook behaves with certain applications that may have unique characteristics.

For example, you can add `acrobat.exe` to those lists to enable text selected in Acrobat.

List types:
- `EXCLUDE_CLIPBOARD_CURSOR_DETECT`: Exclude cursor detection for clipboard operations
- `INCLUDE_CLIPBOARD_DELAY_READ`: Include delay when reading clipboard content

See `SelectionHook.FineTunedListType` constants for details.

#### **`setSelectionPassiveMode(passive: boolean): boolean`**

Set passive mode for selection (only triggered by getCurrentSelection, `text-selection` event will not be emitted).

#### **`writeToClipboard(text: string): boolean`**

Write text to the system clipboard. This is useful for implementing custom copy functions.

#### **`readFromClipboard(): string | null`**

Read text from the system clipboard. Returns clipboard text content as string, or null if clipboard is empty or contains non-text data.

#### **`insertText(text: string): boolean`**

Insert text at the current cursor position in the focused control.

#### **Streaming Insert (recommended for LLM)**

For token-by-token LLM output, prefer the streaming inserter. Each `pushInsertChunk` inserts immediately into the focused control.

#### **`setInsertEventCallback(cb: (event) => void): boolean`**

Register a callback for inserter status events. The callback receives an object like:

`{ type: "insert-status", status: "ended"|"error", streamId, reason?, pid?, hwnd? }`

#### **`beginInsertStream(): string | null`**

Start a streaming insert session and return a `streamId`.

#### **`pushInsertChunk(streamId: string, text: string): boolean`**

Push a text chunk into the stream (inserts immediately).

#### **`endInsertStream(streamId: string): boolean`**

End the stream.

#### **`cancelInsertStream(streamId: string): boolean`**

Cancel the stream.

#### **`closeInserter(): boolean`**

Release inserter resources (mainly the event callback).

#### **`macIsProcessTrusted(): boolean`**

_macOS Only_

Check if the process is trusted for accessibility. If the process is not trusted, the module will still run, but it won't respond to any events. Make sure to guide the user through the authorization process before calling start().

#### **`macRequestProcessTrust(): boolean`**

_macOS Only_

Try to request accessibility permissions. This MAY show a dialog to the user if permissions are not granted.

Note: The return value indicates the current permission status, not the request result.

#### **`isRunning(): boolean`**

Check if the module is currently running.

#### **`cleanup()`**

Release resources and stop monitoring.

### Events

#### **`text-selection`**

Emitted when text is selected, see [`TextSelectionData`](#textselectiondata) for `data` structure

```javascript
hook.on("text-selection", (data: TextSelectionData) => {
  // data contains selection information
});
```

#### **`mouse-move`**, **`mouse-up`**, **`mouse-down`**

Mouse events, see [`MouseEventData`](#mouseeventdata) for `data` structure

```javascript
hook.on("mouse-XXX", (data: MouseEventData) => {
  // data contains mouse coordinates and button info
});
```

#### **`mouse-wheel`**

Mouse wheel events, see [`MouseWheelEventData`](#mousewheeleventdata) for `data` structure

```javascript
hook.on("mouse-wheel", (data: MouseWheelEventData) => {
  // data contains wheel direction info
});
```

#### **`key-down`**, **`key-up`**

Keyboard events, see [`KeyboardEventData`](#keyboardeventdata) for `data` structure

```javascript
hook.on("key-XXX", (data: KeyboardEventData) => {
  // data contains key code and modifier info
});
```

#### **`status`**

Hook status changes

```javascript
hook.on("status", (status: string) => {
  // status is a string, e.g. "started", "stopped"
});
```

#### **`error`**

Error events
Only display errors when `debug` set to `true` when `start()`

```javascript
hook.on("error", (error: Error) => {
  // error is an Error object
});
```

### Data Structure

**Note**: All coordinates are in physical coordinates (virtual screen coordinates) in Windows. You can use `screen.screenToDipPoint(point)` in Electron to convert the point to logical coordinates. In macOS, you don't need to do extra work.

#### `TextSelectionData`

Represents text selection information including content, source application, and coordinates.

| Property        | Type              | Description                                                   |
| --------------- | ----------------- | ------------------------------------------------------------- |
| `text`          | `string`          | The selected text content                                     |
| `programName`   | `string`          | Name of the application where selection occurred              |
| `startTop`      | `Point`           | First paragraph's top-left coordinates (px)                   |
| `startBottom`   | `Point`           | First paragraph's bottom-left coordinates (px)                |
| `endTop`        | `Point`           | Last paragraph's top-right coordinates (px)                   |
| `endBottom`     | `Point`           | Last paragraph's bottom-right coordinates (px)                |
| `mousePosStart` | `Point`           | Mouse position when selection started (px)                    |
| `mousePosEnd`   | `Point`           | Mouse position when selection ended (px)                      |
| `method`        | `SelectionMethod` | Indicates which method was used to detect the text selection. |
| `posLevel`      | `PositionLevel`   | Indicates which positional data is provided.                  |
| `isFullscreen`  | `boolean`         | _macOS Only_ Whether the window is in fullscreen mode         |

Type `Point` is `{ x: number; y: number }`

When `PositionLevel` is:

- `MOUSE_SINGLE`：only `mousePosStart` and `mousePosEnd` is provided, and `mousePosStart` equals `mousePosEnd`
- `MOUSE_DUAL`: only `mousePosStart` and `mousePosEnd` is provided
- `SEL_FULL`: all the mouse position and paragraph's coordinates are provided

#### `MouseEventData`

Contains mouse click/movement information in screen coordinates.

| Property | Type     | Description                                                                                                                     |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `x`      | `number` | Horizontal pointer position (px)                                                                                                |
| `y`      | `number` | Vertical pointer position (px)                                                                                                  |
| `button` | `number` | Same as WebAPIs' MouseEvent.button <br /> `0`=Left, `1`=Middle, `2`=Right, `3`=Back, `4`=Forward <br /> `-1`=None, `99`=Unknown |

If `button != -1` when `mouse-move`, which means it's dragging.

#### `MouseWheelEventData`

Describes mouse wheel scrolling events.

| Property | Type     | Description                         |
| -------- | -------- | ----------------------------------- |
| `button` | `number` | `0`=Vertical, `1`=Horizontal scroll |
| `flag`   | `number` | `1`=Up/Right, `-1`=Down/Left        |

#### `KeyboardEventData`

Represents keyboard key presses/releases.

| Property   | Type      | Description                                                                 |
| ---------- | --------- | --------------------------------------------------------------------------- |
| `uniKey`   | `string`  | Unified key name, refer to MDN `KeyboardEvent.key`, converted from `vkCode` |
| `vkCode`   | `number`  | Virtual key code. Definitions and values vary by platforms.                 |
| `sys`      | `boolean` | Whether modifier keys (Alt/Ctrl/Win/⌘/⌥/Fn) are pressed simultaneously      |
| `scanCode` | `number?` | Hardware scan code. _Windows Only_                                          |
| `flags`    | `number`  | Additional state flags.                                                     |
| `type`     | `string?` | Internal event type                                                         |
| `action`   | `string?` | `"key-down"` or `"key-up"`                                                  |

About vkCode:
- Windows: VK_* values of vkCode
- macOS: kVK_* values of kCGKeyboardEventKeycode

### Constants

#### **`SelectionHook.SelectionMethod`**

Indicates which method was used to detect the text selection:

- `NONE`: No selection detected
- `UIA`: UI Automation (Windows)
- `ACCESSIBLE`: Accessibility interface (Windows)
- `AXAPI`: Accessibility API (macOS)
- `CLIPBOARD`: Clipboard fallback

#### **`SelectionHook.PositionLevel`**

Indicates which positional data is provided:

- `NONE`: No position information
- `MOUSE_SINGLE`: Only single mouse position
- `MOUSE_DUAL`: Mouse start and end positions (when dragging to select)
- `SEL_FULL`: Full selection coordinates (see [`TextSelectionData`](#textselectiondata) structure for details)
- `SEL_DETAILED`: Detailed selection coordinates (reserved for future use)

#### **`SelectionHook.FilterMode`**

- `DEFAULT`: The filter mode is disabled
- `INCLUDE_LIST`: Only the programs in list will pass the filter
- `EXCLUDE_LIST`: Only the programs NOT in list will pass the filter

#### **`SelectionHook.FineTunedListType`**

Defines types for fine-tuned application behavior lists:

- `EXCLUDE_CLIPBOARD_CURSOR_DETECT`: Exclude cursor detection for clipboard operations. Useful for applications with custom cursors (e.g., Adobe Acrobat) where cursor shape detection may not work reliably.
- `INCLUDE_CLIPBOARD_DELAY_READ`: Include delay when reading clipboard content. Useful for applications that modify clipboard content multiple times in quick succession (e.g., Adobe Acrobat).

### TypeScript Support

This module includes TypeScript definitions. See [`index.d.ts`](https://github.com/Quantatirsk/node-selection/blob/main/index.d.ts) for complete type definitions.

## License

MIT License

## Credits

This project is forked from [selection-hook](https://github.com/0xfullex/selection-hook) by 0xfullex.