node-mac-recorder 1.0.5 → 1.2.0
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/README.md +246 -0
- package/binding.gyp +6 -2
- package/cursor-test.js +176 -0
- package/index.js +148 -0
- package/manual-cursor-data.json +352 -0
- package/package.json +1 -1
- package/src/cursor_tracker.mm +399 -0
- package/src/mac_recorder.mm +199 -0
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A powerful native macOS screen recording Node.js package with advanced window se
|
|
|
11
11
|
- 🎯 **Area Selection** - Record custom screen regions
|
|
12
12
|
- 🖱️ **Multi-Display Support** - Automatic display detection and selection
|
|
13
13
|
- 🎨 **Cursor Control** - Toggle cursor visibility in recordings
|
|
14
|
+
- 🖱️ **Cursor Tracking** - Track mouse position, cursor types, and click events
|
|
14
15
|
|
|
15
16
|
🎵 **Granular Audio Controls**
|
|
16
17
|
|
|
@@ -25,6 +26,7 @@ A powerful native macOS screen recording Node.js package with advanced window se
|
|
|
25
26
|
- 🎯 **Automatic Coordinate Conversion** - Handle multi-display coordinate systems
|
|
26
27
|
- 📐 **Display ID Detection** - Automatically select correct display for window recording
|
|
27
28
|
- 🖼️ **Window Filtering** - Smart filtering of recordable windows
|
|
29
|
+
- 👁️ **Preview Thumbnails** - Generate window and display preview images
|
|
28
30
|
|
|
29
31
|
⚙️ **Customization Options**
|
|
30
32
|
|
|
@@ -202,6 +204,92 @@ console.log(status);
|
|
|
202
204
|
// }
|
|
203
205
|
```
|
|
204
206
|
|
|
207
|
+
#### `getWindowThumbnail(windowId, options?)`
|
|
208
|
+
|
|
209
|
+
Captures a thumbnail preview of a specific window.
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
const thumbnail = await recorder.getWindowThumbnail(12345, {
|
|
213
|
+
maxWidth: 400, // Maximum width (default: 300)
|
|
214
|
+
maxHeight: 300, // Maximum height (default: 200)
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Returns: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
|
|
218
|
+
// Can be used directly in <img> tags or saved as file
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `getDisplayThumbnail(displayId, options?)`
|
|
222
|
+
|
|
223
|
+
Captures a thumbnail preview of a specific display.
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const thumbnail = await recorder.getDisplayThumbnail(0, {
|
|
227
|
+
maxWidth: 400, // Maximum width (default: 300)
|
|
228
|
+
maxHeight: 300, // Maximum height (default: 200)
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Returns: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
|
|
232
|
+
// Perfect for display selection UI
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Cursor Tracking Methods
|
|
236
|
+
|
|
237
|
+
#### `startCursorTracking(outputPath)`
|
|
238
|
+
|
|
239
|
+
Starts tracking cursor movements and saves data to JSON file.
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
await recorder.startCursorTracking("./cursor-data.json");
|
|
243
|
+
// Cursor tracking started - will record position, cursor type, and events
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### `stopCursorTracking()`
|
|
247
|
+
|
|
248
|
+
Stops cursor tracking and saves collected data.
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
await recorder.stopCursorTracking();
|
|
252
|
+
// Data saved to specified JSON file
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `getCursorPosition()`
|
|
256
|
+
|
|
257
|
+
Gets current cursor position and type.
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const position = recorder.getCursorPosition();
|
|
261
|
+
console.log(position);
|
|
262
|
+
// {
|
|
263
|
+
// x: 1234,
|
|
264
|
+
// y: 567,
|
|
265
|
+
// cursorType: "default" // "default", "pointer", "grabbing", "text"
|
|
266
|
+
// }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `getCursorTrackingStatus()`
|
|
270
|
+
|
|
271
|
+
Returns cursor tracking status and data count.
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const status = recorder.getCursorTrackingStatus();
|
|
275
|
+
console.log(status);
|
|
276
|
+
// {
|
|
277
|
+
// isTracking: true,
|
|
278
|
+
// dataCount: 1250,
|
|
279
|
+
// hasEventTap: true,
|
|
280
|
+
// hasRunLoopSource: true
|
|
281
|
+
// }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `saveCursorData(outputPath)`
|
|
285
|
+
|
|
286
|
+
Manually saves current cursor data to file.
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
await recorder.saveCursorData("./cursor-backup.json");
|
|
290
|
+
// Data saved to file
|
|
291
|
+
```
|
|
292
|
+
|
|
205
293
|
## Usage Examples
|
|
206
294
|
|
|
207
295
|
### Window-Specific Recording
|
|
@@ -311,6 +399,163 @@ recorder.on("completed", (outputPath) => {
|
|
|
311
399
|
await recorder.startRecording("./event-recording.mov");
|
|
312
400
|
```
|
|
313
401
|
|
|
402
|
+
### Window Selection with Thumbnails
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
const recorder = new MacRecorder();
|
|
406
|
+
|
|
407
|
+
// Get windows with thumbnail previews
|
|
408
|
+
const windows = await recorder.getWindows();
|
|
409
|
+
|
|
410
|
+
console.log("Available windows with previews:");
|
|
411
|
+
for (const window of windows) {
|
|
412
|
+
console.log(`${window.appName} - ${window.name}`);
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
// Generate thumbnail for each window
|
|
416
|
+
const thumbnail = await recorder.getWindowThumbnail(window.id, {
|
|
417
|
+
maxWidth: 200,
|
|
418
|
+
maxHeight: 150,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
console.log(`Thumbnail: ${thumbnail.substring(0, 50)}...`);
|
|
422
|
+
|
|
423
|
+
// Use thumbnail in your UI:
|
|
424
|
+
// <img src="${thumbnail}" alt="Window Preview" />
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.log(`No preview available: ${error.message}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Display Selection Interface
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
const recorder = new MacRecorder();
|
|
435
|
+
|
|
436
|
+
async function createDisplaySelector() {
|
|
437
|
+
const displays = await recorder.getDisplays();
|
|
438
|
+
|
|
439
|
+
const displayOptions = await Promise.all(
|
|
440
|
+
displays.map(async (display, index) => {
|
|
441
|
+
try {
|
|
442
|
+
const thumbnail = await recorder.getDisplayThumbnail(display.id);
|
|
443
|
+
return {
|
|
444
|
+
id: display.id,
|
|
445
|
+
name: `Display ${index + 1}`,
|
|
446
|
+
resolution: display.resolution,
|
|
447
|
+
thumbnail: thumbnail,
|
|
448
|
+
isPrimary: display.isPrimary,
|
|
449
|
+
};
|
|
450
|
+
} catch (error) {
|
|
451
|
+
return {
|
|
452
|
+
id: display.id,
|
|
453
|
+
name: `Display ${index + 1}`,
|
|
454
|
+
resolution: display.resolution,
|
|
455
|
+
thumbnail: null,
|
|
456
|
+
isPrimary: display.isPrimary,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
})
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
return displayOptions;
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Cursor Tracking Usage
|
|
467
|
+
|
|
468
|
+
```javascript
|
|
469
|
+
const recorder = new MacRecorder();
|
|
470
|
+
|
|
471
|
+
async function trackUserInteraction() {
|
|
472
|
+
// Start cursor tracking
|
|
473
|
+
await recorder.startCursorTracking("./user-interactions.json");
|
|
474
|
+
console.log("Cursor tracking started...");
|
|
475
|
+
|
|
476
|
+
// Monitor real-time cursor position
|
|
477
|
+
const monitorInterval = setInterval(() => {
|
|
478
|
+
const position = recorder.getCursorPosition();
|
|
479
|
+
console.log(
|
|
480
|
+
`Cursor: ${position.x}, ${position.y} (${position.cursorType})`
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const status = recorder.getCursorTrackingStatus();
|
|
484
|
+
console.log(`Tracking status: ${status.dataCount} positions recorded`);
|
|
485
|
+
}, 100); // Check every 100ms
|
|
486
|
+
|
|
487
|
+
// Track for 10 seconds
|
|
488
|
+
setTimeout(async () => {
|
|
489
|
+
clearInterval(monitorInterval);
|
|
490
|
+
|
|
491
|
+
// Stop tracking and save data
|
|
492
|
+
await recorder.stopCursorTracking();
|
|
493
|
+
console.log("Cursor tracking completed!");
|
|
494
|
+
|
|
495
|
+
// Load and analyze the data
|
|
496
|
+
const fs = require("fs");
|
|
497
|
+
const data = JSON.parse(
|
|
498
|
+
fs.readFileSync("./user-interactions.json", "utf8")
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
console.log(`Total interactions recorded: ${data.length}`);
|
|
502
|
+
|
|
503
|
+
// Analyze cursor types
|
|
504
|
+
const cursorTypes = {};
|
|
505
|
+
data.forEach((item) => {
|
|
506
|
+
cursorTypes[item.cursorType] = (cursorTypes[item.cursorType] || 0) + 1;
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
console.log("Cursor types distribution:", cursorTypes);
|
|
510
|
+
|
|
511
|
+
// Analyze event types
|
|
512
|
+
const eventTypes = {};
|
|
513
|
+
data.forEach((item) => {
|
|
514
|
+
eventTypes[item.type] = (eventTypes[item.type] || 0) + 1;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
console.log("Event types distribution:", eventTypes);
|
|
518
|
+
}, 10000);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
trackUserInteraction();
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Combined Screen Recording + Cursor Tracking
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
const recorder = new MacRecorder();
|
|
528
|
+
|
|
529
|
+
async function recordWithCursorTracking() {
|
|
530
|
+
// Start both screen recording and cursor tracking
|
|
531
|
+
await Promise.all([
|
|
532
|
+
recorder.startRecording("./screen-recording.mov", {
|
|
533
|
+
captureCursor: false, // Don't show cursor in video
|
|
534
|
+
includeSystemAudio: true,
|
|
535
|
+
quality: "high",
|
|
536
|
+
}),
|
|
537
|
+
recorder.startCursorTracking("./cursor-data.json"),
|
|
538
|
+
]);
|
|
539
|
+
|
|
540
|
+
console.log("Recording screen and tracking cursor...");
|
|
541
|
+
|
|
542
|
+
// Record for 30 seconds
|
|
543
|
+
setTimeout(async () => {
|
|
544
|
+
await Promise.all([
|
|
545
|
+
recorder.stopRecording(),
|
|
546
|
+
recorder.stopCursorTracking(),
|
|
547
|
+
]);
|
|
548
|
+
|
|
549
|
+
console.log("Screen recording and cursor tracking completed!");
|
|
550
|
+
console.log("Files created:");
|
|
551
|
+
console.log("- screen-recording.mov");
|
|
552
|
+
console.log("- cursor-data.json");
|
|
553
|
+
}, 30000);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
recordWithCursorTracking();
|
|
557
|
+
```
|
|
558
|
+
|
|
314
559
|
## Integration Examples
|
|
315
560
|
|
|
316
561
|
### Electron Integration
|
|
@@ -484,6 +729,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
484
729
|
|
|
485
730
|
### Latest Updates
|
|
486
731
|
|
|
732
|
+
- ✅ **Cursor Tracking**: Track mouse position, cursor types, and click events with JSON export
|
|
487
733
|
- ✅ **Window Recording**: Automatic coordinate conversion for multi-display setups
|
|
488
734
|
- ✅ **Audio Controls**: Separate microphone and system audio controls
|
|
489
735
|
- ✅ **Display Selection**: Multi-monitor support with automatic detection
|
package/binding.gyp
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"sources": [
|
|
6
6
|
"src/mac_recorder.mm",
|
|
7
7
|
"src/screen_capture.mm",
|
|
8
|
-
"src/audio_capture.mm"
|
|
8
|
+
"src/audio_capture.mm",
|
|
9
|
+
"src/cursor_tracker.mm"
|
|
9
10
|
],
|
|
10
11
|
"include_dirs": [
|
|
11
12
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
@@ -30,7 +31,10 @@
|
|
|
30
31
|
"-framework CoreVideo",
|
|
31
32
|
"-framework Foundation",
|
|
32
33
|
"-framework AppKit",
|
|
33
|
-
"-framework ScreenCaptureKit"
|
|
34
|
+
"-framework ScreenCaptureKit",
|
|
35
|
+
"-framework ApplicationServices",
|
|
36
|
+
"-framework Carbon",
|
|
37
|
+
"-framework Accessibility"
|
|
34
38
|
]
|
|
35
39
|
},
|
|
36
40
|
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
|
package/cursor-test.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const MacRecorder = require("./index");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
async function testCursorTracking() {
|
|
6
|
+
console.log("🎯 Cursor Tracking Test Başlatılıyor...\n");
|
|
7
|
+
|
|
8
|
+
const recorder = new MacRecorder();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Cursor tracking başlat
|
|
12
|
+
const outputPath = path.join(__dirname, "cursor-data.json");
|
|
13
|
+
console.log("📍 Cursor tracking başlatılıyor...");
|
|
14
|
+
console.log("📁 Output dosyası:", outputPath);
|
|
15
|
+
|
|
16
|
+
await recorder.startCursorTracking(outputPath);
|
|
17
|
+
console.log("✅ Cursor tracking başlatıldı!");
|
|
18
|
+
|
|
19
|
+
// Durum kontrolü
|
|
20
|
+
const status = recorder.getCursorTrackingStatus();
|
|
21
|
+
console.log("📊 Tracking durumu:", status);
|
|
22
|
+
|
|
23
|
+
// 5 saniye bekle ve pozisyon örnekleri al
|
|
24
|
+
console.log(
|
|
25
|
+
"\n🎬 5 saniye boyunca cursor hareketlerinizi takip ediyoruz..."
|
|
26
|
+
);
|
|
27
|
+
console.log("💡 Fare hareket ettirin, tıklayın ve sürükleyin!");
|
|
28
|
+
|
|
29
|
+
// Manuel data collection - JavaScript tarafında polling
|
|
30
|
+
const manualData = [];
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
|
|
33
|
+
for (let i = 5; i > 0; i--) {
|
|
34
|
+
console.log(`⏳ ${i} saniye kaldı...`);
|
|
35
|
+
|
|
36
|
+
// 100ms'de bir pozisyon al (10 FPS)
|
|
37
|
+
for (let j = 0; j < 10; j++) {
|
|
38
|
+
const position = recorder.getCursorPosition();
|
|
39
|
+
const timestamp = Date.now() - startTime;
|
|
40
|
+
|
|
41
|
+
manualData.push({
|
|
42
|
+
x: position.x,
|
|
43
|
+
y: position.y,
|
|
44
|
+
timestamp: timestamp,
|
|
45
|
+
cursorType: position.cursorType,
|
|
46
|
+
type: "move",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Son pozisyonu göster
|
|
53
|
+
if (manualData.length > 0) {
|
|
54
|
+
const lastPos = manualData[manualData.length - 1];
|
|
55
|
+
console.log(
|
|
56
|
+
`📌 Pozisyon: x=${lastPos.x}, y=${lastPos.y}, tip=${lastPos.cursorType}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Manuel veriyi dosyaya kaydet
|
|
62
|
+
console.log(
|
|
63
|
+
`\n📝 Manuel data collection: ${manualData.length} pozisyon toplandı`
|
|
64
|
+
);
|
|
65
|
+
const manualPath = path.join(__dirname, "manual-cursor-data.json");
|
|
66
|
+
fs.writeFileSync(manualPath, JSON.stringify(manualData, null, 2));
|
|
67
|
+
console.log(`📄 Manuel veriler kaydedildi: ${manualPath}`);
|
|
68
|
+
|
|
69
|
+
// Tracking durdur
|
|
70
|
+
console.log("\n🛑 Cursor tracking durduruluyor...");
|
|
71
|
+
await recorder.stopCursorTracking();
|
|
72
|
+
console.log("✅ Cursor tracking durduruldu!");
|
|
73
|
+
|
|
74
|
+
// Final durum kontrolü
|
|
75
|
+
const finalStatus = recorder.getCursorTrackingStatus();
|
|
76
|
+
console.log("📊 Final durumu:", finalStatus);
|
|
77
|
+
|
|
78
|
+
// Dosya kontrolü
|
|
79
|
+
if (fs.existsSync(outputPath)) {
|
|
80
|
+
const data = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
81
|
+
console.log(
|
|
82
|
+
`\n📄 JSON dosyası oluşturuldu: ${data.length} adet cursor verisi kaydedildi`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// İlk birkaç veriyi göster
|
|
86
|
+
if (data.length > 0) {
|
|
87
|
+
console.log("\n📝 İlk 3 cursor verisi:");
|
|
88
|
+
data.slice(0, 3).forEach((item, index) => {
|
|
89
|
+
console.log(
|
|
90
|
+
`${index + 1}. x:${item.x}, y:${item.y}, timestamp:${
|
|
91
|
+
item.timestamp
|
|
92
|
+
}, cursorType:${item.cursorType}, type:${item.type}`
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (data.length > 3) {
|
|
97
|
+
console.log(`... ve ${data.length - 3} adet daha`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Cursor tipleri istatistiği
|
|
102
|
+
const cursorTypes = {};
|
|
103
|
+
const eventTypes = {};
|
|
104
|
+
data.forEach((item) => {
|
|
105
|
+
cursorTypes[item.cursorType] = (cursorTypes[item.cursorType] || 0) + 1;
|
|
106
|
+
eventTypes[item.type] = (eventTypes[item.type] || 0) + 1;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
console.log("\n📈 Cursor Tipleri İstatistiği:");
|
|
110
|
+
Object.keys(cursorTypes).forEach((type) => {
|
|
111
|
+
console.log(` ${type}: ${cursorTypes[type]} adet`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log("\n🎭 Event Tipleri İstatistiği:");
|
|
115
|
+
Object.keys(eventTypes).forEach((type) => {
|
|
116
|
+
console.log(` ${type}: ${eventTypes[type]} adet`);
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
console.log("❌ JSON dosyası oluşturulamadı!");
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("❌ Hata:", error.message);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log("\n🎉 Test tamamlandı!");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Ek fonksiyonlar test et
|
|
129
|
+
async function testCursorPositionOnly() {
|
|
130
|
+
console.log("\n🎯 Anlık Cursor Pozisyon Testi...");
|
|
131
|
+
|
|
132
|
+
const recorder = new MacRecorder();
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
for (let i = 0; i < 5; i++) {
|
|
136
|
+
const position = recorder.getCursorPosition();
|
|
137
|
+
console.log(
|
|
138
|
+
`📌 Pozisyon ${i + 1}: x=${position.x}, y=${position.y}, tip=${
|
|
139
|
+
position.cursorType
|
|
140
|
+
}`
|
|
141
|
+
);
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("❌ Pozisyon alma hatası:", error.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Test menüsü
|
|
150
|
+
async function main() {
|
|
151
|
+
console.log("🚀 MacRecorder Cursor Tracking Test Menüsü\n");
|
|
152
|
+
|
|
153
|
+
const args = process.argv.slice(2);
|
|
154
|
+
|
|
155
|
+
if (args.includes("--position")) {
|
|
156
|
+
await testCursorPositionOnly();
|
|
157
|
+
} else if (args.includes("--full")) {
|
|
158
|
+
await testCursorTracking();
|
|
159
|
+
} else {
|
|
160
|
+
console.log("Kullanım:");
|
|
161
|
+
console.log(
|
|
162
|
+
" node cursor-test.js --full # Tam cursor tracking testi (5 saniye)"
|
|
163
|
+
);
|
|
164
|
+
console.log(
|
|
165
|
+
" node cursor-test.js --position # Sadece anlık pozisyon testi"
|
|
166
|
+
);
|
|
167
|
+
console.log("\nÖrnek:");
|
|
168
|
+
console.log(" node cursor-test.js --full");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (require.main === module) {
|
|
173
|
+
main().catch(console.error);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = { testCursorTracking, testCursorPositionOnly };
|
package/index.js
CHANGED
|
@@ -355,6 +355,154 @@ class MacRecorder extends EventEmitter {
|
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Pencere önizleme görüntüsü alır (Base64 PNG)
|
|
360
|
+
*/
|
|
361
|
+
async getWindowThumbnail(windowId, options = {}) {
|
|
362
|
+
if (!windowId) {
|
|
363
|
+
throw new Error("Window ID is required");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { maxWidth = 300, maxHeight = 200 } = options;
|
|
367
|
+
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
try {
|
|
370
|
+
const base64Image = nativeBinding.getWindowThumbnail(
|
|
371
|
+
windowId,
|
|
372
|
+
maxWidth,
|
|
373
|
+
maxHeight
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
if (base64Image) {
|
|
377
|
+
resolve(`data:image/png;base64,${base64Image}`);
|
|
378
|
+
} else {
|
|
379
|
+
reject(new Error("Failed to capture window thumbnail"));
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
reject(error);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Ekran önizleme görüntüsü alır (Base64 PNG)
|
|
389
|
+
*/
|
|
390
|
+
async getDisplayThumbnail(displayId, options = {}) {
|
|
391
|
+
if (displayId === null || displayId === undefined) {
|
|
392
|
+
throw new Error("Display ID is required");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const { maxWidth = 300, maxHeight = 200 } = options;
|
|
396
|
+
|
|
397
|
+
return new Promise((resolve, reject) => {
|
|
398
|
+
try {
|
|
399
|
+
const base64Image = nativeBinding.getDisplayThumbnail(
|
|
400
|
+
displayId,
|
|
401
|
+
maxWidth,
|
|
402
|
+
maxHeight
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (base64Image) {
|
|
406
|
+
resolve(`data:image/png;base64,${base64Image}`);
|
|
407
|
+
} else {
|
|
408
|
+
reject(new Error("Failed to capture display thumbnail"));
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
reject(error);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Cursor tracking başlatır
|
|
418
|
+
*/
|
|
419
|
+
async startCursorTracking(outputPath) {
|
|
420
|
+
if (!outputPath) {
|
|
421
|
+
throw new Error("Output path is required");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return new Promise((resolve, reject) => {
|
|
425
|
+
try {
|
|
426
|
+
const success = nativeBinding.startCursorTracking(outputPath);
|
|
427
|
+
if (success) {
|
|
428
|
+
this.emit("cursorTrackingStarted", outputPath);
|
|
429
|
+
resolve(true);
|
|
430
|
+
} else {
|
|
431
|
+
reject(
|
|
432
|
+
new Error(
|
|
433
|
+
"Failed to start cursor tracking. Check permissions and try again."
|
|
434
|
+
)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
reject(error);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Cursor tracking durdurur ve JSON dosyasını kaydeder
|
|
445
|
+
*/
|
|
446
|
+
async stopCursorTracking() {
|
|
447
|
+
return new Promise((resolve, reject) => {
|
|
448
|
+
try {
|
|
449
|
+
const success = nativeBinding.stopCursorTracking();
|
|
450
|
+
if (success) {
|
|
451
|
+
this.emit("cursorTrackingStopped");
|
|
452
|
+
resolve(true);
|
|
453
|
+
} else {
|
|
454
|
+
reject(new Error("Failed to stop cursor tracking"));
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
reject(error);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Anlık cursor pozisyonunu ve tipini döndürür
|
|
464
|
+
*/
|
|
465
|
+
getCursorPosition() {
|
|
466
|
+
try {
|
|
467
|
+
return nativeBinding.getCursorPosition();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
throw new Error("Failed to get cursor position: " + error.message);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Cursor tracking durumunu döndürür
|
|
475
|
+
*/
|
|
476
|
+
getCursorTrackingStatus() {
|
|
477
|
+
try {
|
|
478
|
+
return nativeBinding.getCursorTrackingStatus();
|
|
479
|
+
} catch (error) {
|
|
480
|
+
throw new Error("Failed to get cursor tracking status: " + error.message);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Cursor verilerini JSON dosyasına kaydeder
|
|
486
|
+
*/
|
|
487
|
+
async saveCursorData(outputPath) {
|
|
488
|
+
if (!outputPath) {
|
|
489
|
+
throw new Error("Output path is required");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return new Promise((resolve, reject) => {
|
|
493
|
+
try {
|
|
494
|
+
const success = nativeBinding.saveCursorData(outputPath);
|
|
495
|
+
if (success) {
|
|
496
|
+
resolve(true);
|
|
497
|
+
} else {
|
|
498
|
+
reject(new Error("Failed to save cursor data"));
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
reject(error);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
358
506
|
/**
|
|
359
507
|
* Native modül bilgilerini döndürür
|
|
360
508
|
*/
|