@mintplex-labs/advanced-selection-hook 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +9 -0
- package/index.js +23 -0
- package/package.json +6 -11
- package/prebuilds/darwin-arm64/@mintplex-labs+advanced-selection-hook.node +0 -0
- package/prebuilds/darwin-x64/@mintplex-labs+advanced-selection-hook.node +0 -0
- package/prebuilds/win32-arm64/@mintplex-labs+advanced-selection-hook.node +0 -0
- package/prebuilds/win32-x64/@mintplex-labs+advanced-selection-hook.node +0 -0
- package/examples/node-demo.js +0 -727
- package/prebuilds/darwin-arm64/selection-hook.node +0 -0
- package/prebuilds/darwin-x64/selection-hook.node +0 -0
- package/prebuilds/win32-arm64/selection-hook.node +0 -0
- package/prebuilds/win32-x64/selection-hook.node +0 -0
package/index.d.ts
CHANGED
|
@@ -321,6 +321,15 @@ declare class SelectionHook extends EventEmitter {
|
|
|
321
321
|
*/
|
|
322
322
|
readFromClipboard(): string | null;
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Replace the last selected text with new text (Windows only).
|
|
326
|
+
* Uses UI Automation ValuePattern.SetValue so the highlighted range is replaced
|
|
327
|
+
* without focus/clipboard. Only works when the last selection was via UIA in an editable control.
|
|
328
|
+
* @param replacementText - UTF-8 text to insert in place of the selection
|
|
329
|
+
* @returns true if replacement was applied, false otherwise
|
|
330
|
+
*/
|
|
331
|
+
replaceSelectedText(replacementText: string): boolean;
|
|
332
|
+
|
|
324
333
|
/**
|
|
325
334
|
* Check if the process is trusted for accessibility (macOS only)
|
|
326
335
|
*
|
package/index.js
CHANGED
|
@@ -422,6 +422,29 @@ class SelectionHook extends EventEmitter {
|
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Replace the last selected text with new text (Windows only).
|
|
427
|
+
* Uses UI Automation ValuePattern.SetValue to push text into the highlighted range
|
|
428
|
+
* without relying on focus/clipboard. Only works when the last selection was obtained
|
|
429
|
+
* via UI Automation in an editable control.
|
|
430
|
+
* @param {string} replacementText - UTF-8 text to insert in place of the selection
|
|
431
|
+
* @returns {boolean} true if replacement was applied, false otherwise
|
|
432
|
+
*/
|
|
433
|
+
replaceSelectedText(replacementText) {
|
|
434
|
+
if (typeof replacementText !== "string") {
|
|
435
|
+
this.#handleError("replaceSelectedText requires a string", new Error("Invalid argument"));
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (!this.#instance) return false;
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
return this.#instance.replaceSelectedText(replacementText);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
this.#handleError("Failed to replace selected text", err);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
425
448
|
/**
|
|
426
449
|
* Check if the process is trusted for accessibility (macOS only)
|
|
427
450
|
* @returns {boolean} True if the process is trusted for accessibility, false otherwise
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintplex-labs/advanced-selection-hook",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Text selection monitoring of native Node.js module with N-API across applications",
|
|
5
5
|
"author": "@mintplex-labs",
|
|
6
6
|
"repository": {
|
|
@@ -10,21 +10,16 @@
|
|
|
10
10
|
"main": "index.js",
|
|
11
11
|
"types": "index.d.ts",
|
|
12
12
|
"files": [
|
|
13
|
-
"examples",
|
|
14
13
|
"prebuilds",
|
|
15
14
|
"index.d.ts",
|
|
16
15
|
"index.js"
|
|
17
16
|
],
|
|
18
17
|
"scripts": {
|
|
19
|
-
"install": "node-gyp-build",
|
|
20
18
|
"rebuild": "node-gyp rebuild",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"prebuild:mac": "npm run prebuild:mac:x64 && npm run prebuild:mac:arm64",
|
|
26
|
-
"prebuild:mac:x64": "prebuildify --napi --platform=darwin --arch=x64",
|
|
27
|
-
"prebuild:mac:arm64": "prebuildify --napi --platform=darwin --arch=arm64",
|
|
19
|
+
"build:win:x64": "prebuildify --napi --platform=win32 --arch=x64",
|
|
20
|
+
"build:win:arm64": "prebuildify --napi --target 20.0.0 --platform=win32 --arch=arm64",
|
|
21
|
+
"build:mac:x64": "prebuildify --napi --platform=darwin --arch=x64",
|
|
22
|
+
"build:mac:arm64": "prebuildify --napi --platform=darwin --arch=arm64",
|
|
28
23
|
"demo": "node --trace-deprecation --force-node-api-uncaught-exceptions-policy=true examples/node-demo.js",
|
|
29
24
|
"publish:dev": "npm publish --dry-run",
|
|
30
25
|
"publish:prod": "npm publish --access public"
|
|
@@ -59,4 +54,4 @@
|
|
|
59
54
|
"publishConfig": {
|
|
60
55
|
"access": "public"
|
|
61
56
|
}
|
|
62
|
-
}
|
|
57
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/examples/node-demo.js
DELETED
|
@@ -1,727 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Text Selection Hook - Test Application
|
|
3
|
-
*
|
|
4
|
-
* This test file demonstrates the functionality of the Node-API native module
|
|
5
|
-
* for monitoring text selections across applications on Windows.
|
|
6
|
-
*
|
|
7
|
-
* Features demonstrated:
|
|
8
|
-
* - Text selection detection (drag and double-click)
|
|
9
|
-
* - Mouse and keyboard event monitoring
|
|
10
|
-
* - Interactive controls for testing different features
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ===========================
|
|
14
|
-
// === Module Dependencies ===
|
|
15
|
-
// ===========================
|
|
16
|
-
const SelectionHook = require("../index.js");
|
|
17
|
-
|
|
18
|
-
// ===========================
|
|
19
|
-
// === Configuration ========
|
|
20
|
-
// ===========================
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Configuration flags for event display
|
|
24
|
-
* Toggle these using keyboard shortcuts during runtime
|
|
25
|
-
*/
|
|
26
|
-
const config = {
|
|
27
|
-
showMouseMoveEvents: false, // High CPU usage when enabled
|
|
28
|
-
showMouseEvents: false, // Mouse clicks and wheel events
|
|
29
|
-
showKeyboardEvents: false, // Keyboard key press/release events
|
|
30
|
-
clipboardFallbackEnabled: false, // Use clipboard as fallback for text selection
|
|
31
|
-
clipboardMode: 0, // Default clipboard mode (see SelectionHook.FilterMode)
|
|
32
|
-
globalFilterMode: 0, // Default global filter mode (see SelectionHook.FilterMode)
|
|
33
|
-
passiveModeEnabled: false, // Passive mode for text selection
|
|
34
|
-
fineTunedListEnabled: false, // Fine-tuned list for specific application behaviors
|
|
35
|
-
hookStarted: true, // Track if the hook is started (default: ON)
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Add variables for Ctrl key hold detection
|
|
39
|
-
let ctrlKeyPressTime = null;
|
|
40
|
-
let ctrlKeyTriggered = false; // Flag to prevent multiple triggers
|
|
41
|
-
const CTRL_HOLD_THRESHOLD = 500; // 500ms threshold for Ctrl key hold
|
|
42
|
-
|
|
43
|
-
// Program list for clipboard mode and global filter
|
|
44
|
-
const programList = ["cursor.exe", "vscode.exe", "notepad.exe"];
|
|
45
|
-
|
|
46
|
-
// Fine-tuned list for specific application behaviors
|
|
47
|
-
const fineTunedList = ["acrobat.exe", "wps.exe", "cajviewer.exe"];
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* ANSI color codes for console output formatting
|
|
51
|
-
*/
|
|
52
|
-
const colors = {
|
|
53
|
-
info: "\x1b[36m%s\x1b[0m", // Cyan - Used for general information
|
|
54
|
-
success: "\x1b[32m%s\x1b[0m", // Green - Used for successful operations and selections
|
|
55
|
-
warning: "\x1b[33m%s\x1b[0m", // Yellow - Used for position data and toggles
|
|
56
|
-
error: "\x1b[31m%s\x1b[0m", // Red - Used for errors
|
|
57
|
-
highlight: "\x1b[35m%s\x1b[0m", // Magenta - Used for keyboard events
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// ===========================
|
|
61
|
-
// === Initialize Hook ======
|
|
62
|
-
// ===========================
|
|
63
|
-
|
|
64
|
-
// Create instance of the hook
|
|
65
|
-
const hook = new SelectionHook();
|
|
66
|
-
|
|
67
|
-
// Exit if initialization failed
|
|
68
|
-
if (!hook) {
|
|
69
|
-
console.error(colors.error, "Failed to initialize text selection hook");
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ===========================
|
|
74
|
-
// === Core Functions =======
|
|
75
|
-
// ===========================
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Main entry point - initializes and starts the application
|
|
79
|
-
*/
|
|
80
|
-
function main() {
|
|
81
|
-
printWelcomeMessage();
|
|
82
|
-
setupEventListeners();
|
|
83
|
-
startHook();
|
|
84
|
-
setupInputHandlers();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Display welcome message and usage instructions
|
|
89
|
-
*/
|
|
90
|
-
function printWelcomeMessage() {
|
|
91
|
-
console.log(colors.info, "=== Text Selection Hook Test ===");
|
|
92
|
-
console.log("Testing mouse action detection for text selection");
|
|
93
|
-
|
|
94
|
-
console.log("\nPlease perform the following actions:");
|
|
95
|
-
console.log("1. Drag to select text (distance > 8px) - should log 'action_drag_selection'");
|
|
96
|
-
console.log("2. Double-click to select text - should log 'action_dblclick_selection'");
|
|
97
|
-
|
|
98
|
-
console.log("\nCoordinates Information:");
|
|
99
|
-
console.log(" - startTop/startBottom: First paragraph's left-top and left-bottom points");
|
|
100
|
-
console.log(" - endTop/endBottom: Last paragraph's right-top and right-bottom points");
|
|
101
|
-
console.log(" - mousePosStart: Initial mouse position when selection started");
|
|
102
|
-
console.log(" - mousePosEnd: Final mouse position when selection ended");
|
|
103
|
-
|
|
104
|
-
console.log("\nKeyboard Controls:");
|
|
105
|
-
console.log(" S - Toggle start/stop hook (default: ON)");
|
|
106
|
-
console.log(" M - Toggle mouse move events (default: OFF)");
|
|
107
|
-
console.log(" E - Toggle other mouse events (default: OFF)");
|
|
108
|
-
console.log(" K - Toggle keyboard events (default: OFF)");
|
|
109
|
-
console.log(" C - Display current selection");
|
|
110
|
-
console.log(" B - Toggle clipboard fallback (default: OFF)");
|
|
111
|
-
console.log(" L - Toggle clipboard mode (DEFAULT → EXCLUDE_LIST → INCLUDE_LIST)");
|
|
112
|
-
console.log(" F - Toggle global filter mode (DEFAULT → EXCLUDE_LIST → INCLUDE_LIST)");
|
|
113
|
-
console.log(" P - Toggle passive mode (default: OFF)");
|
|
114
|
-
console.log(" T - Toggle fine-tuned list (default: OFF)");
|
|
115
|
-
console.log(" W - Write text 'Test clipboard write from selection-hook' to clipboard");
|
|
116
|
-
console.log(" R - Read text from clipboard");
|
|
117
|
-
console.log(" A - Check accessibility permissions (macOS only)");
|
|
118
|
-
console.log(" Q - Request accessibility permissions (macOS only)");
|
|
119
|
-
console.log(" ? - Show help");
|
|
120
|
-
console.log(" Ctrl+C - Exit program");
|
|
121
|
-
console.log("\nIn passive mode, press & hold Ctrl key to trigger text selection");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Start the hook and begin listening for events
|
|
126
|
-
*/
|
|
127
|
-
function startHook() {
|
|
128
|
-
if (hook.start({ debug: true })) {
|
|
129
|
-
console.log(colors.success, "Text selection listener started successfully");
|
|
130
|
-
console.log(colors.info, "Mouse move events are disabled by default (press 'M' to toggle)");
|
|
131
|
-
} else {
|
|
132
|
-
console.error(colors.error, "Failed to start text selection listener");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Clean up resources and exit the application
|
|
138
|
-
*/
|
|
139
|
-
function cleanup() {
|
|
140
|
-
console.log(colors.info, "\nStopping text selection listener...");
|
|
141
|
-
|
|
142
|
-
// Make sure to disable mouse move events before stopping to reduce resource usage
|
|
143
|
-
if (config.showMouseMoveEvents) {
|
|
144
|
-
hook.disableMouseMoveEvent();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Disable clipboard fallback if enabled
|
|
148
|
-
if (config.clipboardFallbackEnabled) {
|
|
149
|
-
hook.disableClipboard();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
hook.stop();
|
|
153
|
-
hook.cleanup();
|
|
154
|
-
process.exit(0);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ===========================
|
|
158
|
-
// === Event Listeners ======
|
|
159
|
-
// ===========================
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Set up all event listeners for the hook
|
|
163
|
-
*/
|
|
164
|
-
function setupEventListeners() {
|
|
165
|
-
// Text selection events - our primary functionality
|
|
166
|
-
hook.on("text-selection", (selectionData) => {
|
|
167
|
-
showSelection(selectionData);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Status events from the native module
|
|
171
|
-
hook.on("status", (status) => {
|
|
172
|
-
console.log(colors.info, "Listener status:", status);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// Setup mouse events
|
|
176
|
-
setupMouseEventListeners();
|
|
177
|
-
|
|
178
|
-
// Setup keyboard events
|
|
179
|
-
setupKeyboardEventListeners();
|
|
180
|
-
|
|
181
|
-
// Error events
|
|
182
|
-
hook.on("error", (error) => {
|
|
183
|
-
console.error(colors.error, "Error:", error.message);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Set up mouse-related event listeners
|
|
189
|
-
*/
|
|
190
|
-
function setupMouseEventListeners() {
|
|
191
|
-
// Mouse move events (high CPU usage - disabled by default)
|
|
192
|
-
hook.on("mouse-move", (eventData) => {
|
|
193
|
-
if (config.showMouseMoveEvents) {
|
|
194
|
-
console.log(
|
|
195
|
-
colors.warning,
|
|
196
|
-
"Mouse move:",
|
|
197
|
-
`x: ${eventData.x}, y: ${eventData.y}, button: ${eventData.button}`
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Mouse button events
|
|
203
|
-
hook.on("mouse-up", (eventData) => {
|
|
204
|
-
if (config.showMouseEvents) {
|
|
205
|
-
console.log(
|
|
206
|
-
colors.warning,
|
|
207
|
-
"Mouse up:",
|
|
208
|
-
`button: ${eventData.button}, x: ${eventData.x}, y: ${eventData.y}`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
hook.on("mouse-down", (eventData) => {
|
|
214
|
-
if (config.showMouseEvents) {
|
|
215
|
-
console.log(
|
|
216
|
-
colors.warning,
|
|
217
|
-
"Mouse down:",
|
|
218
|
-
`button: ${eventData.button}, x: ${eventData.x}, y: ${eventData.y}`
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Mouse wheel events
|
|
224
|
-
hook.on("mouse-wheel", (eventData) => {
|
|
225
|
-
if (config.showMouseEvents) {
|
|
226
|
-
console.log(
|
|
227
|
-
colors.warning,
|
|
228
|
-
"Mouse wheel:",
|
|
229
|
-
`button: ${eventData.button}, direction: ${eventData.flag > 0 ? "up/right" : "down/left"}`
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Set up keyboard-related event listeners
|
|
237
|
-
*/
|
|
238
|
-
function setupKeyboardEventListeners() {
|
|
239
|
-
// Key down events
|
|
240
|
-
hook.on("key-down", (eventData) => {
|
|
241
|
-
if (config.showKeyboardEvents) {
|
|
242
|
-
console.log(
|
|
243
|
-
colors.highlight,
|
|
244
|
-
"Key down:",
|
|
245
|
-
`uniKey: ${eventData.uniKey}, vkCode: ${eventData.vkCode}, scanCode: ${
|
|
246
|
-
eventData.scanCode
|
|
247
|
-
}, flags: ${eventData.flags}${eventData.sys ? ", system key" : ""}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Handle Ctrl+C for exit
|
|
252
|
-
if (eventData.vkCode === 67 && eventData.flags & 0x0001) {
|
|
253
|
-
// 67 is 'C', 0x0001 is Ctrl flag
|
|
254
|
-
cleanup();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Special handling for Ctrl key (vkCode 162) to get current selection in passive mode
|
|
259
|
-
if (eventData.vkCode === 162 && config.passiveModeEnabled) {
|
|
260
|
-
const currentTime = Date.now();
|
|
261
|
-
|
|
262
|
-
// Initialize press time if not set
|
|
263
|
-
if (ctrlKeyPressTime === null) {
|
|
264
|
-
ctrlKeyPressTime = currentTime;
|
|
265
|
-
ctrlKeyTriggered = false; // Reset trigger flag
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Check if held long enough and not already triggered
|
|
270
|
-
const holdDuration = currentTime - ctrlKeyPressTime;
|
|
271
|
-
if (holdDuration >= CTRL_HOLD_THRESHOLD && !ctrlKeyTriggered) {
|
|
272
|
-
console.log(
|
|
273
|
-
colors.info,
|
|
274
|
-
`Ctrl held for ${holdDuration}ms, requesting current selection in passive mode`
|
|
275
|
-
);
|
|
276
|
-
const selectionData = hook.getCurrentSelection();
|
|
277
|
-
if (selectionData) {
|
|
278
|
-
showSelection(selectionData);
|
|
279
|
-
} else {
|
|
280
|
-
console.log(colors.warning, "No selection data available");
|
|
281
|
-
}
|
|
282
|
-
ctrlKeyTriggered = true; // Set flag to prevent further triggers
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Key up events
|
|
288
|
-
hook.on("key-up", (eventData) => {
|
|
289
|
-
if (config.showKeyboardEvents) {
|
|
290
|
-
console.log(
|
|
291
|
-
colors.highlight,
|
|
292
|
-
"Key up:",
|
|
293
|
-
`uniKey: ${eventData.uniKey}, vkCode: ${eventData.vkCode}, scanCode: ${
|
|
294
|
-
eventData.scanCode
|
|
295
|
-
}, flags: ${eventData.flags}${eventData.sys ? ", system key" : ""}`
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Reset variables when Ctrl is released
|
|
300
|
-
if (eventData.vkCode === 162 && config.passiveModeEnabled) {
|
|
301
|
-
ctrlKeyPressTime = null;
|
|
302
|
-
ctrlKeyTriggered = false;
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ===========================
|
|
308
|
-
// === User Input Handling ==
|
|
309
|
-
// ===========================
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Configure handlers for user input
|
|
313
|
-
*/
|
|
314
|
-
function setupInputHandlers() {
|
|
315
|
-
// Set up standard input in raw mode for immediate key handling
|
|
316
|
-
process.stdin.setRawMode(true);
|
|
317
|
-
process.stdin.resume();
|
|
318
|
-
process.stdin.on("data", handleKeyPress);
|
|
319
|
-
|
|
320
|
-
// Handle Ctrl+C for graceful exit
|
|
321
|
-
process.on("SIGINT", cleanup);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Process keyboard input from the user
|
|
326
|
-
* @param {Buffer} key - The key buffer from stdin
|
|
327
|
-
*/
|
|
328
|
-
function handleKeyPress(key) {
|
|
329
|
-
const keyStr = key.toString();
|
|
330
|
-
|
|
331
|
-
// Exit on Ctrl+C
|
|
332
|
-
if (keyStr === "\u0003") {
|
|
333
|
-
cleanup();
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
switch (keyStr.toLowerCase()) {
|
|
338
|
-
case "s": // Toggle start/stop hook
|
|
339
|
-
toggleHookStartStop();
|
|
340
|
-
break;
|
|
341
|
-
|
|
342
|
-
case "m": // Toggle mouse move events
|
|
343
|
-
toggleMouseMoveEvents();
|
|
344
|
-
break;
|
|
345
|
-
|
|
346
|
-
case "e": // Toggle other mouse events
|
|
347
|
-
config.showMouseEvents = !config.showMouseEvents;
|
|
348
|
-
console.log(colors.success, `Mouse events: ${config.showMouseEvents ? "ON" : "OFF"}`);
|
|
349
|
-
break;
|
|
350
|
-
|
|
351
|
-
case "k": // Toggle keyboard events
|
|
352
|
-
config.showKeyboardEvents = !config.showKeyboardEvents;
|
|
353
|
-
console.log(colors.success, `Keyboard events: ${config.showKeyboardEvents ? "ON" : "OFF"}`);
|
|
354
|
-
break;
|
|
355
|
-
|
|
356
|
-
case "c": // Display current selection
|
|
357
|
-
const selectionData = hook.getCurrentSelection();
|
|
358
|
-
if (selectionData) {
|
|
359
|
-
console.log(colors.success, "Current selection retrieved");
|
|
360
|
-
showSelection(selectionData);
|
|
361
|
-
} else {
|
|
362
|
-
console.log(colors.warning, "No current selection available");
|
|
363
|
-
}
|
|
364
|
-
break;
|
|
365
|
-
|
|
366
|
-
case "b": // Toggle clipboard fallback
|
|
367
|
-
config.clipboardFallbackEnabled = !config.clipboardFallbackEnabled;
|
|
368
|
-
|
|
369
|
-
if (config.clipboardFallbackEnabled) {
|
|
370
|
-
// Enable clipboard fallback in the native module
|
|
371
|
-
const success = hook.enableClipboard();
|
|
372
|
-
if (success) {
|
|
373
|
-
console.log(colors.success, "Clipboard fallback: ENABLED");
|
|
374
|
-
} else {
|
|
375
|
-
config.clipboardFallbackEnabled = false;
|
|
376
|
-
console.log(colors.error, "Failed to enable clipboard fallback");
|
|
377
|
-
}
|
|
378
|
-
} else {
|
|
379
|
-
// Disable clipboard fallback in the native module
|
|
380
|
-
const success = hook.disableClipboard();
|
|
381
|
-
if (success) {
|
|
382
|
-
console.log(colors.warning, "Clipboard fallback: DISABLED");
|
|
383
|
-
} else {
|
|
384
|
-
console.log(colors.error, "Failed to disable clipboard fallback");
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
|
|
389
|
-
case "l": // Toggle clipboard mode
|
|
390
|
-
toggleClipboardMode();
|
|
391
|
-
break;
|
|
392
|
-
|
|
393
|
-
case "f": // Toggle global filter mode
|
|
394
|
-
toggleGlobalFilterMode();
|
|
395
|
-
break;
|
|
396
|
-
|
|
397
|
-
case "p": // Toggle passive mode
|
|
398
|
-
togglePassiveMode();
|
|
399
|
-
break;
|
|
400
|
-
|
|
401
|
-
case "t": // Toggle fine-tuned list
|
|
402
|
-
toggleFineTunedList();
|
|
403
|
-
break;
|
|
404
|
-
|
|
405
|
-
case "w":
|
|
406
|
-
// Write test text to clipboard
|
|
407
|
-
const testText = "Test clipboard write from selection-hook";
|
|
408
|
-
const success = hook.writeToClipboard(testText);
|
|
409
|
-
if (success) {
|
|
410
|
-
console.log(colors.success, `Successfully wrote text to clipboard: "${testText}"`);
|
|
411
|
-
} else {
|
|
412
|
-
console.log(colors.error, "Failed to write text to clipboard");
|
|
413
|
-
}
|
|
414
|
-
break;
|
|
415
|
-
|
|
416
|
-
case "r":
|
|
417
|
-
// Read text from clipboard
|
|
418
|
-
const clipboardText = hook.readFromClipboard();
|
|
419
|
-
if (clipboardText) {
|
|
420
|
-
console.log(colors.success, `Text read from clipboard: "${clipboardText}"`);
|
|
421
|
-
} else {
|
|
422
|
-
console.log(colors.warning, "Failed to read text from clipboard");
|
|
423
|
-
}
|
|
424
|
-
break;
|
|
425
|
-
|
|
426
|
-
case "a":
|
|
427
|
-
// Check accessibility permissions (macOS only)
|
|
428
|
-
if (process.platform === "darwin") {
|
|
429
|
-
const isTrusted = hook.macIsProcessTrusted();
|
|
430
|
-
if (isTrusted) {
|
|
431
|
-
console.log(colors.success, "Process is trusted for accessibility on macOS");
|
|
432
|
-
} else {
|
|
433
|
-
console.log(colors.warning, "Process is NOT trusted for accessibility on macOS");
|
|
434
|
-
console.log(
|
|
435
|
-
colors.info,
|
|
436
|
-
"You may need to grant accessibility permissions in System Preferences"
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
} else {
|
|
440
|
-
console.log(colors.info, "Accessibility permission check is only available on macOS");
|
|
441
|
-
}
|
|
442
|
-
break;
|
|
443
|
-
|
|
444
|
-
case "q":
|
|
445
|
-
// Request accessibility permissions (macOS only)
|
|
446
|
-
if (process.platform === "darwin") {
|
|
447
|
-
console.log(colors.info, "Requesting accessibility permissions...");
|
|
448
|
-
const currentStatus = hook.macRequestProcessTrust();
|
|
449
|
-
if (currentStatus) {
|
|
450
|
-
console.log(colors.success, "Process is already trusted for accessibility on macOS");
|
|
451
|
-
} else {
|
|
452
|
-
console.log(colors.warning, "Process is not trusted for accessibility on macOS");
|
|
453
|
-
console.log(colors.info, "A system dialog may have appeared to grant permissions");
|
|
454
|
-
console.log(
|
|
455
|
-
colors.info,
|
|
456
|
-
"Please check System Preferences > Security & Privacy > Accessibility"
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
console.log(colors.info, "Accessibility permission request is only available on macOS");
|
|
461
|
-
}
|
|
462
|
-
break;
|
|
463
|
-
|
|
464
|
-
case "?":
|
|
465
|
-
// Show help
|
|
466
|
-
printWelcomeMessage();
|
|
467
|
-
break;
|
|
468
|
-
|
|
469
|
-
default:
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Toggle hook start/stop
|
|
476
|
-
* Controls whether the hook is actively listening for events
|
|
477
|
-
*/
|
|
478
|
-
function toggleHookStartStop() {
|
|
479
|
-
config.hookStarted = !config.hookStarted;
|
|
480
|
-
|
|
481
|
-
if (config.hookStarted) {
|
|
482
|
-
// Start the hook
|
|
483
|
-
const success = hook.start({ debug: true });
|
|
484
|
-
if (success) {
|
|
485
|
-
console.log(colors.success, "Text selection hook: STARTED");
|
|
486
|
-
} else {
|
|
487
|
-
config.hookStarted = false; // Revert the toggle
|
|
488
|
-
console.log(colors.error, "Failed to start text selection hook");
|
|
489
|
-
}
|
|
490
|
-
} else {
|
|
491
|
-
// Stop the hook
|
|
492
|
-
const success = hook.stop();
|
|
493
|
-
if (success) {
|
|
494
|
-
console.log(colors.warning, "Text selection hook: STOPPED");
|
|
495
|
-
} else {
|
|
496
|
-
config.hookStarted = true; // Revert the toggle
|
|
497
|
-
console.log(colors.error, "Failed to stop text selection hook");
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Toggle mouse move event tracking
|
|
504
|
-
* Mouse move events use significant CPU resources, so they're disabled by default
|
|
505
|
-
*/
|
|
506
|
-
function toggleMouseMoveEvents() {
|
|
507
|
-
config.showMouseMoveEvents = !config.showMouseMoveEvents;
|
|
508
|
-
|
|
509
|
-
if (config.showMouseMoveEvents) {
|
|
510
|
-
// Enable mouse move events in the native module
|
|
511
|
-
const success = hook.enableMouseMoveEvent();
|
|
512
|
-
if (success) {
|
|
513
|
-
console.log(colors.success, "Mouse move events: ENABLED");
|
|
514
|
-
} else {
|
|
515
|
-
config.showMouseMoveEvents = false;
|
|
516
|
-
console.log(colors.error, "Failed to enable mouse move events");
|
|
517
|
-
}
|
|
518
|
-
} else {
|
|
519
|
-
// Disable mouse move events in the native module
|
|
520
|
-
const success = hook.disableMouseMoveEvent();
|
|
521
|
-
if (success) {
|
|
522
|
-
console.log(colors.warning, "Mouse move events: DISABLED");
|
|
523
|
-
} else {
|
|
524
|
-
console.log(
|
|
525
|
-
colors.error,
|
|
526
|
-
"Failed to disable mouse move events, but events won't be displayed"
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Toggle passive mode for text selection
|
|
534
|
-
* In passive mode, the hook doesn't automatically monitor selections
|
|
535
|
-
* and requires manual triggering (e.g., with Alt key)
|
|
536
|
-
*/
|
|
537
|
-
function togglePassiveMode() {
|
|
538
|
-
config.passiveModeEnabled = !config.passiveModeEnabled;
|
|
539
|
-
|
|
540
|
-
// Set passive mode in the native module
|
|
541
|
-
const success = hook.setSelectionPassiveMode(config.passiveModeEnabled);
|
|
542
|
-
if (success) {
|
|
543
|
-
console.log(
|
|
544
|
-
colors.success,
|
|
545
|
-
`Passive mode: ${config.passiveModeEnabled ? "ENABLED" : "DISABLED"}`
|
|
546
|
-
);
|
|
547
|
-
if (config.passiveModeEnabled) {
|
|
548
|
-
console.log(colors.info, "Press & hold Ctrl key to trigger text selection manually");
|
|
549
|
-
}
|
|
550
|
-
} else {
|
|
551
|
-
config.passiveModeEnabled = !config.passiveModeEnabled; // Revert the toggle
|
|
552
|
-
console.log(colors.error, "Failed to set passive mode");
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Toggle fine-tuned list for specific application behaviors
|
|
558
|
-
*/
|
|
559
|
-
function toggleFineTunedList() {
|
|
560
|
-
config.fineTunedListEnabled = !config.fineTunedListEnabled;
|
|
561
|
-
|
|
562
|
-
const programList = config.fineTunedListEnabled ? fineTunedList : [];
|
|
563
|
-
|
|
564
|
-
// Set fine-tuned list for both types simultaneously
|
|
565
|
-
const excludeClipboardSuccess = hook.setFineTunedList(
|
|
566
|
-
SelectionHook.FineTunedListType.EXCLUDE_CLIPBOARD_CURSOR_DETECT,
|
|
567
|
-
programList
|
|
568
|
-
);
|
|
569
|
-
|
|
570
|
-
const includeDelaySuccess = hook.setFineTunedList(
|
|
571
|
-
SelectionHook.FineTunedListType.INCLUDE_CLIPBOARD_DELAY_READ,
|
|
572
|
-
programList
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
if (excludeClipboardSuccess && includeDelaySuccess) {
|
|
576
|
-
console.log(
|
|
577
|
-
colors.success,
|
|
578
|
-
`Fine-tuned list: ${config.fineTunedListEnabled ? "ENABLED" : "DISABLED"}`
|
|
579
|
-
);
|
|
580
|
-
if (config.fineTunedListEnabled) {
|
|
581
|
-
console.log(colors.info, `Programs in fine-tuned list: ${fineTunedList.join(", ")}`);
|
|
582
|
-
console.log(
|
|
583
|
-
colors.info,
|
|
584
|
-
"Applied to both EXCLUDE_CLIPBOARD_CURSOR_DETECT and INCLUDE_CLIPBOARD_DELAY_READ"
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
} else {
|
|
588
|
-
config.fineTunedListEnabled = !config.fineTunedListEnabled; // Revert the toggle
|
|
589
|
-
console.log(colors.error, "Failed to set fine-tuned list");
|
|
590
|
-
if (!excludeClipboardSuccess) {
|
|
591
|
-
console.log(colors.error, "- EXCLUDE_CLIPBOARD_CURSOR_DETECT failed");
|
|
592
|
-
}
|
|
593
|
-
if (!includeDelaySuccess) {
|
|
594
|
-
console.log(colors.error, "- INCLUDE_CLIPBOARD_DELAY_READ failed");
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Get clipboard mode name from numeric value
|
|
601
|
-
* @param {number} mode - Clipboard mode value
|
|
602
|
-
* @returns {string} Mode name
|
|
603
|
-
*/
|
|
604
|
-
function getFilterModeName(mode) {
|
|
605
|
-
const modeNames = {
|
|
606
|
-
[SelectionHook.FilterMode.DEFAULT]: "DEFAULT",
|
|
607
|
-
[SelectionHook.FilterMode.EXCLUDE_LIST]: "EXCLUDE_LIST",
|
|
608
|
-
[SelectionHook.FilterMode.INCLUDE_LIST]: "INCLUDE_LIST",
|
|
609
|
-
};
|
|
610
|
-
return modeNames[mode] || "UNKNOWN";
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Toggle clipboard mode (DEFAULT -> EXCLUDE_LIST -> INCLUDE_LIST -> DEFAULT)
|
|
615
|
-
*/
|
|
616
|
-
function toggleClipboardMode() {
|
|
617
|
-
// Rotate through the modes
|
|
618
|
-
config.clipboardMode = (config.clipboardMode + 1) % 3;
|
|
619
|
-
|
|
620
|
-
// Apply the new mode if clipboard fallback is enabled
|
|
621
|
-
const success = hook.setClipboardMode(config.clipboardMode, programList);
|
|
622
|
-
if (success) {
|
|
623
|
-
console.log(
|
|
624
|
-
colors.success,
|
|
625
|
-
`Clipboard mode set to ${getFilterModeName(config.clipboardMode)} for ${programList.join(
|
|
626
|
-
", "
|
|
627
|
-
)}`
|
|
628
|
-
);
|
|
629
|
-
} else {
|
|
630
|
-
console.log(colors.error, "Failed to set clipboard mode");
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Toggle global filter mode (DEFAULT -> EXCLUDE_LIST -> INCLUDE_LIST -> DEFAULT)
|
|
636
|
-
*/
|
|
637
|
-
function toggleGlobalFilterMode() {
|
|
638
|
-
// Rotate through the modes
|
|
639
|
-
config.globalFilterMode = (config.globalFilterMode + 1) % 3;
|
|
640
|
-
|
|
641
|
-
// Apply the new mode
|
|
642
|
-
const success = hook.setGlobalFilterMode(config.globalFilterMode, programList);
|
|
643
|
-
if (success) {
|
|
644
|
-
console.log(
|
|
645
|
-
colors.success,
|
|
646
|
-
`Global filter mode set to ${getFilterModeName(
|
|
647
|
-
config.globalFilterMode
|
|
648
|
-
)} for ${programList.join(", ")}`
|
|
649
|
-
);
|
|
650
|
-
} else {
|
|
651
|
-
console.log(colors.error, "Failed to set global filter mode");
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// ===========================
|
|
656
|
-
// === Utility Functions ====
|
|
657
|
-
// ===========================
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Display text selection data
|
|
661
|
-
* @param {Object} selectionData - Selection data from the hook
|
|
662
|
-
*/
|
|
663
|
-
function showSelection(selectionData) {
|
|
664
|
-
if (!selectionData) {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
console.log(colors.info, `=== Detected selected text (${selectionData.text.length}) ===`);
|
|
669
|
-
|
|
670
|
-
console.log(selectionData.text.replace(/\r\n/g, "\n").replace(/\r(?!\n)/g, "\n"));
|
|
671
|
-
|
|
672
|
-
// Selection method and position level maps
|
|
673
|
-
const methodMap = {
|
|
674
|
-
0: "None",
|
|
675
|
-
1: "UI Automation",
|
|
676
|
-
2: "Focus Control",
|
|
677
|
-
3: "Accessibility",
|
|
678
|
-
99: "Clipboard",
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
const posLevelMap = {
|
|
682
|
-
0: "None",
|
|
683
|
-
1: "Mouse Single",
|
|
684
|
-
2: "Mouse Dual",
|
|
685
|
-
3: "Selection Full",
|
|
686
|
-
4: "Selection Detailed",
|
|
687
|
-
};
|
|
688
|
-
|
|
689
|
-
console.log(colors.warning, "=== Selection Information ===");
|
|
690
|
-
console.log(colors.warning, "Program Name:", selectionData.programName);
|
|
691
|
-
console.log(
|
|
692
|
-
colors.warning,
|
|
693
|
-
"Method: ",
|
|
694
|
-
`${methodMap[selectionData.method] || selectionData.method}`
|
|
695
|
-
);
|
|
696
|
-
console.log(
|
|
697
|
-
colors.warning,
|
|
698
|
-
"Position Level: ",
|
|
699
|
-
`${posLevelMap[selectionData.posLevel] || selectionData.posLevel}`
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
// Display all available coordinates
|
|
703
|
-
if (selectionData.posLevel >= 1) {
|
|
704
|
-
console.log(
|
|
705
|
-
colors.warning,
|
|
706
|
-
"Mouse: ",
|
|
707
|
-
`(${selectionData.mousePosStart.x}, ${selectionData.mousePosStart.y}) -> (${selectionData.mousePosEnd.x}, ${selectionData.mousePosEnd.y})`
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
if (selectionData.posLevel >= 3) {
|
|
711
|
-
console.log(
|
|
712
|
-
colors.warning,
|
|
713
|
-
` startTop(${selectionData.startTop.x}, ${selectionData.startTop.y})\t endTop(${selectionData.endTop.x}, ${selectionData.endTop.y})`
|
|
714
|
-
);
|
|
715
|
-
console.log(
|
|
716
|
-
colors.warning,
|
|
717
|
-
`startBottom(${selectionData.startBottom.x}, ${selectionData.startBottom.y})\tendBottom(${selectionData.endBottom.x}, ${selectionData.endBottom.y})`
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// ===========================
|
|
723
|
-
// === Start Application ====
|
|
724
|
-
// ===========================
|
|
725
|
-
|
|
726
|
-
// Launch the application
|
|
727
|
-
main();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|