node-mac-recorder 2.17.19 → 2.17.21

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.
Files changed (41) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/index.js +21 -200
  3. package/package.json +1 -1
  4. package/publish.sh +18 -0
  5. package/src/cursor_tracker.mm +37 -20
  6. package/cursor-data-1751364226346.json +0 -1
  7. package/cursor-data-1751364314136.json +0 -1
  8. package/cursor-data.json +0 -1
  9. package/cursor-debug-test.js +0 -60
  10. package/cursor-dpr-test.js +0 -73
  11. package/cursor-dpr-test.json +0 -1
  12. package/cursor-macbook-test.js +0 -63
  13. package/cursor-permission-test.js +0 -46
  14. package/cursor-scaling-debug.js +0 -53
  15. package/cursor-simple-test.js +0 -26
  16. package/debug-audio.js +0 -79
  17. package/debug-coordinates.js +0 -69
  18. package/debug-cursor-output.json +0 -1
  19. package/debug-macos14.js +0 -110
  20. package/debug-primary-window.js +0 -84
  21. package/debug-screen-selection.js +0 -81
  22. package/debug-window-selector.js +0 -178
  23. package/examples/electron-integration-example.js +0 -230
  24. package/examples/electron-preload.js +0 -46
  25. package/examples/electron-renderer.html +0 -634
  26. package/examples/integration-example.js +0 -228
  27. package/examples/window-selector-example.js +0 -254
  28. package/quick-cursor-test.json +0 -1
  29. package/test-both-cursor.json +0 -1
  30. package/test-cursor-fix.js +0 -105
  31. package/test-cursor-types.js +0 -117
  32. package/test-display-coordinates.js +0 -72
  33. package/test-integrated-recording.js +0 -120
  34. package/test-menubar-offset.js +0 -110
  35. package/test-output/primary-fix-test-1758266910543.json +0 -1
  36. package/test-output/unified-cursor-1758313640878.json +0 -1
  37. package/test-output/unified-cursor-1758313689471.json +0 -1
  38. package/test-primary-cursor.js +0 -154
  39. package/test-primary-fix.js +0 -120
  40. package/test-unified-cursor.js +0 -75
  41. package/test-window-recording.js +0 -149
@@ -1,634 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Electron-Safe Mac Recorder</title>
7
- <style>
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
10
- sans-serif;
11
- margin: 0;
12
- padding: 20px;
13
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
- color: white;
15
- min-height: 100vh;
16
- }
17
-
18
- .container {
19
- max-width: 1000px;
20
- margin: 0 auto;
21
- background: rgba(255, 255, 255, 0.1);
22
- backdrop-filter: blur(10px);
23
- border-radius: 20px;
24
- padding: 30px;
25
- box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
26
- }
27
-
28
- h1 {
29
- text-align: center;
30
- margin-bottom: 30px;
31
- font-size: 2.5em;
32
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
33
- }
34
-
35
- .status-bar {
36
- background: rgba(255, 255, 255, 0.2);
37
- padding: 15px;
38
- border-radius: 10px;
39
- margin-bottom: 20px;
40
- display: flex;
41
- justify-content: space-between;
42
- align-items: center;
43
- }
44
-
45
- .recording-controls {
46
- display: flex;
47
- gap: 15px;
48
- margin-bottom: 30px;
49
- flex-wrap: wrap;
50
- }
51
-
52
- .btn {
53
- padding: 12px 24px;
54
- border: none;
55
- border-radius: 8px;
56
- cursor: pointer;
57
- font-size: 16px;
58
- font-weight: 600;
59
- transition: all 0.3s ease;
60
- backdrop-filter: blur(10px);
61
- }
62
-
63
- .btn-primary {
64
- background: rgba(76, 175, 80, 0.8);
65
- color: white;
66
- }
67
-
68
- .btn-danger {
69
- background: rgba(244, 67, 54, 0.8);
70
- color: white;
71
- }
72
-
73
- .btn-secondary {
74
- background: rgba(255, 255, 255, 0.2);
75
- color: white;
76
- }
77
-
78
- .btn:hover {
79
- transform: translateY(-2px);
80
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
81
- }
82
-
83
- .btn:disabled {
84
- opacity: 0.5;
85
- cursor: not-allowed;
86
- transform: none;
87
- }
88
-
89
- .options-panel {
90
- background: rgba(255, 255, 255, 0.1);
91
- padding: 20px;
92
- border-radius: 10px;
93
- margin-bottom: 20px;
94
- }
95
-
96
- .form-group {
97
- margin-bottom: 15px;
98
- }
99
-
100
- label {
101
- display: block;
102
- margin-bottom: 5px;
103
- font-weight: 600;
104
- }
105
-
106
- input[type="checkbox"] {
107
- margin-right: 8px;
108
- transform: scale(1.2);
109
- }
110
-
111
- select {
112
- width: 100%;
113
- padding: 8px;
114
- border-radius: 5px;
115
- border: none;
116
- background: rgba(255, 255, 255, 0.9);
117
- color: #333;
118
- }
119
-
120
- .displays-grid,
121
- .windows-grid {
122
- display: grid;
123
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
124
- gap: 15px;
125
- margin-top: 15px;
126
- }
127
-
128
- .display-item,
129
- .window-item {
130
- background: rgba(255, 255, 255, 0.1);
131
- padding: 15px;
132
- border-radius: 10px;
133
- cursor: pointer;
134
- transition: all 0.3s ease;
135
- border: 2px solid transparent;
136
- }
137
-
138
- .display-item:hover,
139
- .window-item:hover {
140
- background: rgba(255, 255, 255, 0.2);
141
- transform: translateY(-2px);
142
- }
143
-
144
- .display-item.selected,
145
- .window-item.selected {
146
- border-color: #4caf50;
147
- background: rgba(76, 175, 80, 0.2);
148
- }
149
-
150
- .thumbnail {
151
- width: 100%;
152
- height: 120px;
153
- object-fit: cover;
154
- border-radius: 5px;
155
- margin-bottom: 10px;
156
- background: rgba(255, 255, 255, 0.1);
157
- }
158
-
159
- .info-section {
160
- background: rgba(255, 255, 255, 0.1);
161
- padding: 20px;
162
- border-radius: 10px;
163
- margin-bottom: 20px;
164
- }
165
-
166
- .permission-item {
167
- display: flex;
168
- justify-content: space-between;
169
- margin-bottom: 10px;
170
- }
171
-
172
- .status-indicator {
173
- padding: 4px 12px;
174
- border-radius: 15px;
175
- font-size: 12px;
176
- font-weight: 600;
177
- }
178
-
179
- .status-granted {
180
- background: rgba(76, 175, 80, 0.8);
181
- }
182
-
183
- .status-denied {
184
- background: rgba(244, 67, 54, 0.8);
185
- }
186
-
187
- .log-area {
188
- background: rgba(0, 0, 0, 0.3);
189
- padding: 15px;
190
- border-radius: 10px;
191
- height: 200px;
192
- overflow-y: auto;
193
- font-family: "Monaco", "Consolas", monospace;
194
- font-size: 12px;
195
- line-height: 1.4;
196
- }
197
-
198
- .recording-time {
199
- font-size: 1.5em;
200
- font-weight: bold;
201
- color: #ff4444;
202
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
203
- }
204
-
205
- .sections {
206
- display: grid;
207
- grid-template-columns: 1fr 1fr;
208
- gap: 20px;
209
- }
210
-
211
- @media (max-width: 768px) {
212
- .sections {
213
- grid-template-columns: 1fr;
214
- }
215
-
216
- .recording-controls {
217
- justify-content: center;
218
- }
219
- }
220
- </style>
221
- </head>
222
- <body>
223
- <div class="container">
224
- <h1>🎬 Electron-Safe Mac Recorder</h1>
225
-
226
- <div class="status-bar">
227
- <div>
228
- <strong>Status:</strong> <span id="recordingStatus">Idle</span>
229
- </div>
230
- <div class="recording-time" id="recordingTime">00:00</div>
231
- </div>
232
-
233
- <div class="recording-controls">
234
- <button id="startBtn" class="btn btn-primary">
235
- 🎬 Start Recording
236
- </button>
237
- <button id="stopBtn" class="btn btn-danger" disabled>
238
- 🛑 Stop Recording
239
- </button>
240
- <button id="refreshBtn" class="btn btn-secondary">🔄 Refresh</button>
241
- <button id="permissionsBtn" class="btn btn-secondary">
242
- 🔐 Check Permissions
243
- </button>
244
- </div>
245
-
246
- <div class="options-panel">
247
- <h3>📋 Recording Options</h3>
248
- <div class="form-group">
249
- <label>
250
- <input type="checkbox" id="captureCursor" /> Capture Cursor
251
- </label>
252
- </div>
253
- <div class="form-group">
254
- <label>
255
- <input type="checkbox" id="includeMicrophone" /> Include Microphone
256
- </label>
257
- </div>
258
- <div class="form-group">
259
- <label>
260
- <input type="checkbox" id="includeSystemAudio" /> Include System
261
- Audio
262
- </label>
263
- </div>
264
- </div>
265
-
266
- <div class="sections">
267
- <div>
268
- <div class="info-section">
269
- <h3>📺 Displays</h3>
270
- <div class="displays-grid" id="displaysGrid">
271
- <div>Loading displays...</div>
272
- </div>
273
- </div>
274
-
275
- <div class="info-section">
276
- <h3>🔐 Permissions</h3>
277
- <div id="permissionsInfo">
278
- <div>Checking permissions...</div>
279
- </div>
280
- </div>
281
- </div>
282
-
283
- <div>
284
- <div class="info-section">
285
- <h3>🪟 Windows</h3>
286
- <div class="windows-grid" id="windowsGrid">
287
- <div>Loading windows...</div>
288
- </div>
289
- </div>
290
-
291
- <div class="info-section">
292
- <h3>📜 Activity Log</h3>
293
- <div class="log-area" id="logArea"></div>
294
- </div>
295
- </div>
296
- </div>
297
- </div>
298
-
299
- <script>
300
- // Application state
301
- let selectedDisplayId = null;
302
- let selectedWindowId = null;
303
- let isRecording = false;
304
- let recordingStartTime = 0;
305
- let timerInterval = null;
306
-
307
- // UI Elements
308
- const startBtn = document.getElementById("startBtn");
309
- const stopBtn = document.getElementById("stopBtn");
310
- const refreshBtn = document.getElementById("refreshBtn");
311
- const permissionsBtn = document.getElementById("permissionsBtn");
312
- const recordingStatus = document.getElementById("recordingStatus");
313
- const recordingTime = document.getElementById("recordingTime");
314
- const logArea = document.getElementById("logArea");
315
- const displaysGrid = document.getElementById("displaysGrid");
316
- const windowsGrid = document.getElementById("windowsGrid");
317
- const permissionsInfo = document.getElementById("permissionsInfo");
318
-
319
- // Logging function
320
- function log(message, type = "info") {
321
- const timestamp = new Date().toLocaleTimeString();
322
- const logEntry = `[${timestamp}] ${message}\n`;
323
- logArea.textContent += logEntry;
324
- logArea.scrollTop = logArea.scrollHeight;
325
- console.log(message);
326
- }
327
-
328
- // Format time as MM:SS
329
- function formatTime(seconds) {
330
- const mins = Math.floor(seconds / 60);
331
- const secs = seconds % 60;
332
- return `${mins.toString().padStart(2, "0")}:${secs
333
- .toString()
334
- .padStart(2, "0")}`;
335
- }
336
-
337
- // Update recording timer
338
- function updateTimer() {
339
- if (isRecording) {
340
- const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
341
- recordingTime.textContent = formatTime(elapsed);
342
- }
343
- }
344
-
345
- // Event Listeners Setup
346
- window.electronAPI.recorder.onRecordingStarted((event, data) => {
347
- log("🎬 Recording started successfully");
348
- isRecording = true;
349
- recordingStartTime = Date.now();
350
- recordingStatus.textContent = "Recording";
351
- startBtn.disabled = true;
352
- stopBtn.disabled = false;
353
-
354
- timerInterval = setInterval(updateTimer, 1000);
355
- });
356
-
357
- window.electronAPI.recorder.onRecordingStopped((event, result) => {
358
- log("🛑 Recording stopped");
359
- isRecording = false;
360
- recordingStatus.textContent = "Stopped";
361
- startBtn.disabled = false;
362
- stopBtn.disabled = true;
363
-
364
- if (timerInterval) {
365
- clearInterval(timerInterval);
366
- timerInterval = null;
367
- }
368
- });
369
-
370
- window.electronAPI.recorder.onRecordingCompleted((event, outputPath) => {
371
- log(`✅ Recording completed: ${outputPath}`);
372
- recordingStatus.textContent = "Completed";
373
- recordingTime.textContent = "00:00";
374
- });
375
-
376
- window.electronAPI.recorder.onTimeUpdate((event, elapsed) => {
377
- recordingTime.textContent = formatTime(elapsed);
378
- });
379
-
380
- // Button Event Handlers
381
- startBtn.addEventListener("click", async () => {
382
- try {
383
- const saveDialog = await window.electronAPI.dialog.showSaveDialog();
384
- if (saveDialog.canceled) {
385
- return;
386
- }
387
-
388
- const options = {
389
- captureCursor: document.getElementById("captureCursor").checked,
390
- includeMicrophone:
391
- document.getElementById("includeMicrophone").checked,
392
- includeSystemAudio:
393
- document.getElementById("includeSystemAudio").checked,
394
- displayId: selectedDisplayId,
395
- windowId: selectedWindowId,
396
- };
397
-
398
- log(`🎬 Starting recording with options: ${JSON.stringify(options)}`);
399
- const result = await window.electronAPI.recorder.startRecording(
400
- saveDialog.filePath,
401
- options
402
- );
403
-
404
- if (!result.success) {
405
- log(`❌ Failed to start recording: ${result.error}`, "error");
406
- }
407
- } catch (error) {
408
- log(`❌ Error starting recording: ${error.message}`, "error");
409
- }
410
- });
411
-
412
- stopBtn.addEventListener("click", async () => {
413
- try {
414
- log("🛑 Stopping recording...");
415
- const result = await window.electronAPI.recorder.stopRecording();
416
-
417
- if (!result.success) {
418
- log(`❌ Failed to stop recording: ${result.error}`, "error");
419
- }
420
- } catch (error) {
421
- log(`❌ Error stopping recording: ${error.message}`, "error");
422
- }
423
- });
424
-
425
- refreshBtn.addEventListener("click", () => {
426
- log("🔄 Refreshing...");
427
- loadDisplays();
428
- loadWindows();
429
- checkPermissions();
430
- });
431
-
432
- permissionsBtn.addEventListener("click", checkPermissions);
433
-
434
- // Load displays
435
- async function loadDisplays() {
436
- try {
437
- const displays = await window.electronAPI.recorder.getDisplays();
438
- displaysGrid.innerHTML = "";
439
-
440
- for (const display of displays) {
441
- const displayDiv = document.createElement("div");
442
- displayDiv.className = "display-item";
443
- displayDiv.onclick = () => selectDisplay(display.id, displayDiv);
444
-
445
- try {
446
- const thumbnail =
447
- await window.electronAPI.recorder.getDisplayThumbnail(
448
- display.id,
449
- { maxWidth: 200, maxHeight: 120 }
450
- );
451
- displayDiv.innerHTML = `
452
- <img src="${thumbnail}" class="thumbnail" alt="Display ${
453
- display.id
454
- }">
455
- <div><strong>${display.name}</strong></div>
456
- <div>${display.width}x${display.height}</div>
457
- <div>${
458
- display.isPrimary ? "🖥️ Primary" : "📱 Secondary"
459
- }</div>
460
- `;
461
- } catch (error) {
462
- displayDiv.innerHTML = `
463
- <div class="thumbnail"></div>
464
- <div><strong>${display.name}</strong></div>
465
- <div>${display.width}x${display.height}</div>
466
- <div>${
467
- display.isPrimary ? "🖥️ Primary" : "📱 Secondary"
468
- }</div>
469
- `;
470
- }
471
-
472
- displaysGrid.appendChild(displayDiv);
473
- }
474
-
475
- log(`📺 Loaded ${displays.length} displays`);
476
- } catch (error) {
477
- log(`❌ Error loading displays: ${error.message}`, "error");
478
- displaysGrid.innerHTML = "<div>Failed to load displays</div>";
479
- }
480
- }
481
-
482
- // Load windows
483
- async function loadWindows() {
484
- try {
485
- const windows = await window.electronAPI.recorder.getWindows();
486
- windowsGrid.innerHTML = "";
487
-
488
- for (const window of windows) {
489
- const windowDiv = document.createElement("div");
490
- windowDiv.className = "window-item";
491
- windowDiv.onclick = () => selectWindow(window.id, windowDiv);
492
-
493
- try {
494
- const thumbnail =
495
- await window.electronAPI.recorder.getWindowThumbnail(
496
- window.id,
497
- { maxWidth: 200, maxHeight: 120 }
498
- );
499
- windowDiv.innerHTML = `
500
- <img src="${thumbnail}" class="thumbnail" alt="${window.name}">
501
- <div><strong>${window.name}</strong></div>
502
- <div>${window.appName}</div>
503
- <div>${window.width}x${window.height}</div>
504
- `;
505
- } catch (error) {
506
- windowDiv.innerHTML = `
507
- <div class="thumbnail"></div>
508
- <div><strong>${window.name}</strong></div>
509
- <div>${window.appName}</div>
510
- <div>${window.width}x${window.height}</div>
511
- `;
512
- }
513
-
514
- windowsGrid.appendChild(windowDiv);
515
- }
516
-
517
- log(`🪟 Loaded ${windows.length} windows`);
518
- } catch (error) {
519
- log(`❌ Error loading windows: ${error.message}`, "error");
520
- windowsGrid.innerHTML = "<div>Failed to load windows</div>";
521
- }
522
- }
523
-
524
- // Check permissions
525
- async function checkPermissions() {
526
- try {
527
- const permissions =
528
- await window.electronAPI.recorder.checkPermissions();
529
-
530
- permissionsInfo.innerHTML = `
531
- <div class="permission-item">
532
- <span>Screen Recording:</span>
533
- <span class="status-indicator ${
534
- permissions.screenRecording
535
- ? "status-granted"
536
- : "status-denied"
537
- }">
538
- ${
539
- permissions.screenRecording ? "Granted" : "Denied"
540
- }
541
- </span>
542
- </div>
543
- <div class="permission-item">
544
- <span>Accessibility:</span>
545
- <span class="status-indicator ${
546
- permissions.accessibility
547
- ? "status-granted"
548
- : "status-denied"
549
- }">
550
- ${permissions.accessibility ? "Granted" : "Denied"}
551
- </span>
552
- </div>
553
- <div class="permission-item">
554
- <span>Microphone:</span>
555
- <span class="status-indicator ${
556
- permissions.microphone
557
- ? "status-granted"
558
- : "status-denied"
559
- }">
560
- ${permissions.microphone ? "Granted" : "Denied"}
561
- </span>
562
- </div>
563
- `;
564
-
565
- log("🔐 Permissions checked");
566
- } catch (error) {
567
- log(`❌ Error checking permissions: ${error.message}`, "error");
568
- permissionsInfo.innerHTML = "<div>Failed to check permissions</div>";
569
- }
570
- }
571
-
572
- // Select display
573
- function selectDisplay(displayId, element) {
574
- // Clear previous selections
575
- document
576
- .querySelectorAll(".display-item")
577
- .forEach((item) => item.classList.remove("selected"));
578
- document
579
- .querySelectorAll(".window-item")
580
- .forEach((item) => item.classList.remove("selected"));
581
-
582
- selectedDisplayId = displayId;
583
- selectedWindowId = null;
584
- element.classList.add("selected");
585
- log(`📺 Selected display: ${displayId}`);
586
- }
587
-
588
- // Select window
589
- function selectWindow(windowId, element) {
590
- // Clear previous selections
591
- document
592
- .querySelectorAll(".display-item")
593
- .forEach((item) => item.classList.remove("selected"));
594
- document
595
- .querySelectorAll(".window-item")
596
- .forEach((item) => item.classList.remove("selected"));
597
-
598
- selectedWindowId = windowId;
599
- selectedDisplayId = null;
600
- element.classList.add("selected");
601
- log(`🪟 Selected window: ${windowId}`);
602
- }
603
-
604
- // Initialize app
605
- async function init() {
606
- try {
607
- const moduleInfo = await window.electronAPI.recorder.getModuleInfo();
608
- log(
609
- `🔌 Module initialized: ${moduleInfo.version} (Electron-safe: ${moduleInfo.electronSafe})`
610
- );
611
-
612
- await loadDisplays();
613
- await loadWindows();
614
- await checkPermissions();
615
-
616
- log("✅ Application initialized successfully");
617
- } catch (error) {
618
- log(`❌ Initialization error: ${error.message}`, "error");
619
- }
620
- }
621
-
622
- // Start the app
623
- init();
624
-
625
- // Cleanup on page unload
626
- window.addEventListener("beforeunload", () => {
627
- if (timerInterval) {
628
- clearInterval(timerInterval);
629
- }
630
- window.electronAPI.recorder.removeAllListeners();
631
- });
632
- </script>
633
- </body>
634
- </html>