node-mac-recorder 2.4.10 → 2.4.11

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.
@@ -34,40 +34,72 @@ const selector = new ElectronWindowSelector();
34
34
  console.log(`Environment: ${selector.isElectron ? 'Electron' : 'Node.js'}`);
35
35
  ```
36
36
 
37
- ### Pencere Seçimi (Electron Safe Mode)
37
+ ### Real-Time Window Detection (Electron Mode)
38
38
  ```javascript
39
39
  // 1. Mevcut pencere listesini al
40
40
  const windows = await selector.getAvailableWindows();
41
41
  console.log(`Found ${windows.length} windows`);
42
42
 
43
- // 2. Pencere seçim başlat (otomatik mode - demo amaçlı)
44
- const selectedWindow = await selector.selectWindow();
43
+ // 2. Real-time mouse tracking başlat
44
+ const nativeBinding = require('./build/Release/mac_recorder.node');
45
+ const trackingInterval = setInterval(() => {
46
+ const status = nativeBinding.getWindowSelectionStatus();
47
+
48
+ if (status && status.currentWindow) {
49
+ const window = status.currentWindow;
50
+ console.log(`Window under cursor: ${window.appName} - "${window.title}"`);
51
+ console.log(`Position: (${window.x}, ${window.y}) Size: ${window.width}x${window.height}`);
52
+
53
+ if (window.screenId !== undefined) {
54
+ console.log(`Screen: ${window.screenId} (${window.screenWidth}x${window.screenHeight})`);
55
+ }
56
+
57
+ // Electron UI'da bu window'u real-time highlight et
58
+ highlightWindowInElectronUI(window);
59
+ }
60
+ }, 100); // 100ms polling for smooth tracking
45
61
 
46
- // 3. Event listener ile dinle
47
- selector.on('windowSelected', (windowInfo) => {
48
- console.log('Selected:', windowInfo.title, windowInfo.appName);
62
+ // 3. Pencere seçimi tamamlandığında
63
+ function selectWindow(windowInfo) {
64
+ clearInterval(trackingInterval);
65
+ console.log('Window selected:', windowInfo);
49
66
 
50
- // Electron UI'da bu window'u highlight et
51
- showWindowInElectronUI(windowInfo);
52
- });
67
+ // Recording başlat
68
+ startRecordingWithWindow(windowInfo);
69
+ }
53
70
  ```
54
71
 
55
- ### Display Seçimi (Electron Safe Mode)
72
+ ### Screen/Display Seçimi (Electron Mode)
56
73
  ```javascript
57
74
  // 1. Mevcut display listesini al
58
75
  const displays = await selector.getAvailableDisplays();
59
76
  console.log(`Found ${displays.length} displays`);
60
77
 
61
- // 2. Display seçim başlat (otomatik mode - demo amaçlı)
62
- const selectedDisplay = await selector.selectScreen();
78
+ // 2. Screen seçim başlat (Electron mode'da otomatik main screen)
79
+ const nativeBinding = require('./build/Release/mac_recorder.node');
80
+ const screenResult = nativeBinding.startScreenSelection();
63
81
 
64
- // 3. Event listener ile dinle
65
- selector.on('screenSelected', (screenInfo) => {
66
- console.log('Selected Display:', screenInfo.name, screenInfo.resolution);
82
+ if (screenResult) {
83
+ // Screen seçim bilgisini al
84
+ const selectedScreen = nativeBinding.getSelectedScreenInfo();
67
85
 
68
- // Electron UI'da bu display'i highlight et
69
- showDisplayInElectronUI(screenInfo);
70
- });
86
+ if (selectedScreen) {
87
+ console.log('Screen selected:', selectedScreen);
88
+ console.log(`Resolution: ${selectedScreen.width}x${selectedScreen.height}`);
89
+ console.log(`Position: (${selectedScreen.x}, ${selectedScreen.y})`);
90
+
91
+ // Screen recording başlat
92
+ startScreenRecording(selectedScreen);
93
+ }
94
+ }
95
+
96
+ // 3. Manuel screen seçimi (UI ile)
97
+ function selectScreen(screenInfo) {
98
+ console.log('Screen manually selected:', screenInfo);
99
+
100
+ // Recording başlat
101
+ startScreenRecordingWithScreen(screenInfo);
102
+ }
71
103
  ```
72
104
 
73
105
  ## 🎨 Electron UI Implementation Önerisi
@@ -104,12 +136,14 @@ selector.on('screenSelected', (screenInfo) => {
104
136
  </div>
105
137
  ```
106
138
 
107
- ### 2. Renderer Process Logic
139
+ ### 2. Real-Time Window Tracking (Renderer Process)
108
140
  ```javascript
109
141
  // renderer.js
110
142
  const { ipcRenderer } = require('electron');
111
143
 
112
144
  let windowSelector = null;
145
+ let trackingInterval = null;
146
+ let currentHighlightedWindow = null;
113
147
 
114
148
  async function initializeWindowPicker() {
115
149
  // Main process'ten window selector'ı başlat
@@ -119,6 +153,77 @@ async function initializeWindowPicker() {
119
153
  // Mevcut windows'ları al
120
154
  const windows = await ipcRenderer.invoke('get-available-windows');
121
155
  displayWindowsInUI(windows);
156
+
157
+ // Real-time tracking başlat
158
+ startRealTimeTracking();
159
+ }
160
+ }
161
+
162
+ function startRealTimeTracking() {
163
+ trackingInterval = setInterval(async () => {
164
+ try {
165
+ // Main process'ten mouse altındaki pencere bilgisini al
166
+ const currentWindow = await ipcRenderer.invoke('get-current-window-under-cursor');
167
+
168
+ if (currentWindow && currentWindow.id !== currentHighlightedWindow?.id) {
169
+ // Yeni pencere tespit edildi
170
+ highlightWindowInUI(currentWindow);
171
+ currentHighlightedWindow = currentWindow;
172
+
173
+ // UI'da bilgileri güncelle
174
+ updateCurrentWindowInfo(currentWindow);
175
+ } else if (!currentWindow && currentHighlightedWindow) {
176
+ // Mouse hiçbir pencere üstünde değil
177
+ clearWindowHighlight();
178
+ currentHighlightedWindow = null;
179
+ clearCurrentWindowInfo();
180
+ }
181
+ } catch (error) {
182
+ console.warn('Window tracking error:', error.message);
183
+ }
184
+ }, 100); // 100ms smooth tracking
185
+ }
186
+
187
+ function highlightWindowInUI(window) {
188
+ // Tüm window card'ları normal hale getir
189
+ document.querySelectorAll('.window-card').forEach(card => {
190
+ card.classList.remove('hover-detected');
191
+ });
192
+
193
+ // Eşleşen window card'ı highlight et
194
+ const matchingCard = document.querySelector(`[data-window-id="${window.id}"]`);
195
+ if (matchingCard) {
196
+ matchingCard.classList.add('hover-detected');
197
+ }
198
+ }
199
+
200
+ function clearWindowHighlight() {
201
+ document.querySelectorAll('.window-card').forEach(card => {
202
+ card.classList.remove('hover-detected');
203
+ });
204
+ }
205
+
206
+ function updateCurrentWindowInfo(window) {
207
+ const infoDiv = document.getElementById('current-window-info');
208
+ if (infoDiv) {
209
+ infoDiv.innerHTML = `
210
+ <h4>Window Under Cursor</h4>
211
+ <p><strong>App:</strong> ${window.appName}</p>
212
+ <p><strong>Title:</strong> ${window.title}</p>
213
+ <p><strong>Size:</strong> ${window.width}×${window.height}</p>
214
+ <p><strong>Position:</strong> (${window.x}, ${window.y})</p>
215
+ ${window.screenId !== undefined ?
216
+ `<p><strong>Screen:</strong> ${window.screenId} (${window.screenWidth}×${window.screenHeight})</p>` :
217
+ ''}
218
+ `;
219
+ infoDiv.classList.add('visible');
220
+ }
221
+ }
222
+
223
+ function clearCurrentWindowInfo() {
224
+ const infoDiv = document.getElementById('current-window-info');
225
+ if (infoDiv) {
226
+ infoDiv.classList.remove('visible');
122
227
  }
123
228
  }
124
229
 
@@ -135,6 +240,7 @@ function displayWindowsInUI(windows) {
135
240
  function createWindowCard(window) {
136
241
  const card = document.createElement('div');
137
242
  card.className = 'window-card';
243
+ card.setAttribute('data-window-id', window.id);
138
244
  card.innerHTML = `
139
245
  <div class="window-thumbnail">
140
246
  <div class="window-placeholder">${window.appName?.charAt(0) || '?'}</div>
@@ -151,28 +257,50 @@ function createWindowCard(window) {
151
257
  }
152
258
 
153
259
  function selectWindow(window) {
260
+ // Tracking'i durdur
261
+ if (trackingInterval) {
262
+ clearInterval(trackingInterval);
263
+ trackingInterval = null;
264
+ }
265
+
154
266
  // UI'da seçimi görsel olarak göster
155
- document.querySelectorAll('.window-card').forEach(card =>
156
- card.classList.remove('selected')
157
- );
267
+ document.querySelectorAll('.window-card').forEach(card => {
268
+ card.classList.remove('selected', 'hover-detected');
269
+ });
158
270
  event.target.closest('.window-card').classList.add('selected');
159
271
 
160
272
  // Main process'e seçimi bildir
161
273
  ipcRenderer.invoke('window-selected', window);
162
274
  }
275
+
276
+ // Cleanup function
277
+ window.addEventListener('beforeunload', () => {
278
+ if (trackingInterval) {
279
+ clearInterval(trackingInterval);
280
+ }
281
+ });
163
282
  ```
164
283
 
165
- ### 3. Main Process Handler
284
+ ### 3. Enhanced Main Process Handler
166
285
  ```javascript
167
286
  // main.js
168
287
  const { ipcMain } = require('electron');
169
288
  const ElectronWindowSelector = require('node-mac-recorder/electron-window-selector');
170
289
 
171
290
  let windowSelector = null;
291
+ let nativeBinding = null;
172
292
 
173
293
  ipcMain.handle('init-window-selector', async () => {
174
294
  try {
175
295
  windowSelector = new ElectronWindowSelector();
296
+
297
+ // Native binding'i yükle (real-time tracking için)
298
+ try {
299
+ nativeBinding = require('./build/Release/mac_recorder.node');
300
+ } catch (error) {
301
+ console.warn('Native binding yüklenemedi:', error.message);
302
+ }
303
+
176
304
  return { success: true };
177
305
  } catch (error) {
178
306
  return { success: false, error: error.message };
@@ -189,20 +317,99 @@ ipcMain.handle('get-available-displays', async () => {
189
317
  return await windowSelector.getAvailableDisplays();
190
318
  });
191
319
 
320
+ // Real-time window tracking için yeni handler
321
+ ipcMain.handle('get-current-window-under-cursor', async () => {
322
+ if (!nativeBinding) return null;
323
+
324
+ try {
325
+ const status = nativeBinding.getWindowSelectionStatus();
326
+ return status?.currentWindow || null;
327
+ } catch (error) {
328
+ console.warn('Window status alınamadı:', error.message);
329
+ return null;
330
+ }
331
+ });
332
+
192
333
  ipcMain.handle('window-selected', async (event, windowInfo) => {
193
334
  console.log('Window selected in Electron UI:', windowInfo);
335
+ console.log(` - App: ${windowInfo.appName}`);
336
+ console.log(` - Title: ${windowInfo.title}`);
337
+ console.log(` - Size: ${windowInfo.width}×${windowInfo.height}`);
338
+ console.log(` - Position: (${windowInfo.x}, ${windowInfo.y})`);
339
+
340
+ if (windowInfo.screenId !== undefined) {
341
+ console.log(` - Screen: ${windowInfo.screenId} (${windowInfo.screenWidth}×${windowInfo.screenHeight})`);
342
+ }
194
343
 
195
344
  // Recording başlatılabilir
196
345
  const MacRecorder = require('node-mac-recorder');
197
346
  const recorder = new MacRecorder();
198
347
 
199
- // Window recording başlat
200
- await recorder.startRecording('./output.mov', {
201
- windowId: windowInfo.id,
202
- // ... diğer options
203
- });
348
+ try {
349
+ // Window recording başlat
350
+ await recorder.startRecording('./output.mov', {
351
+ windowId: windowInfo.id,
352
+ // Screen coordination
353
+ x: windowInfo.x,
354
+ y: windowInfo.y,
355
+ width: windowInfo.width,
356
+ height: windowInfo.height,
357
+ // Diğer options
358
+ fps: 30,
359
+ audioEnabled: true
360
+ });
361
+
362
+ return {
363
+ success: true,
364
+ message: 'Recording started successfully',
365
+ windowInfo: windowInfo
366
+ };
367
+ } catch (error) {
368
+ return {
369
+ success: false,
370
+ error: error.message
371
+ };
372
+ }
373
+ });
374
+
375
+ // Screen recording handler
376
+ ipcMain.handle('screen-selected', async (event, screenInfo) => {
377
+ console.log('Screen selected in Electron UI:', screenInfo);
378
+
379
+ const MacRecorder = require('node-mac-recorder');
380
+ const recorder = new MacRecorder();
204
381
 
205
- return { success: true };
382
+ try {
383
+ // Screen recording başlat
384
+ await recorder.startRecording('./screen-output.mov', {
385
+ // Screen mode
386
+ screenId: screenInfo.id,
387
+ x: screenInfo.x,
388
+ y: screenInfo.y,
389
+ width: screenInfo.width,
390
+ height: screenInfo.height,
391
+ fps: 30,
392
+ audioEnabled: true
393
+ });
394
+
395
+ return {
396
+ success: true,
397
+ message: 'Screen recording started',
398
+ screenInfo: screenInfo
399
+ };
400
+ } catch (error) {
401
+ return {
402
+ success: false,
403
+ error: error.message
404
+ };
405
+ }
406
+ });
407
+
408
+ // Recording control handlers
409
+ ipcMain.handle('stop-recording', async () => {
410
+ // Aktif recorder instance'ı durdur
411
+ // Bu implementation'a recorder management eklenmeli
412
+ return { success: true, message: 'Recording stopped' };
206
413
  });
207
414
  ```
208
415
 
@@ -243,12 +450,13 @@ function showRecordingPreview(windowInfo) {
243
450
  }
244
451
  ```
245
452
 
246
- ## 🔧 CSS Styling
453
+ ## 🎨 Enhanced CSS Styling (Real-Time Tracking)
247
454
  ```css
248
455
  .window-picker {
249
456
  padding: 20px;
250
457
  max-height: 500px;
251
458
  overflow-y: auto;
459
+ position: relative;
252
460
  }
253
461
 
254
462
  .window-grid {
@@ -264,6 +472,7 @@ function showRecordingPreview(windowInfo) {
264
472
  padding: 10px;
265
473
  cursor: pointer;
266
474
  transition: all 0.2s;
475
+ position: relative;
267
476
  }
268
477
 
269
478
  .window-card:hover {
@@ -276,6 +485,27 @@ function showRecordingPreview(windowInfo) {
276
485
  background-color: #e6f3ff;
277
486
  }
278
487
 
488
+ /* Real-time hover detection styling */
489
+ .window-card.hover-detected {
490
+ border-color: #ff6b35 !important;
491
+ background-color: #fff3f0 !important;
492
+ box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
493
+ transform: translateY(-2px);
494
+ }
495
+
496
+ .window-card.hover-detected::before {
497
+ content: "🎯 CURSOR HERE";
498
+ position: absolute;
499
+ top: -10px;
500
+ right: 5px;
501
+ background: #ff6b35;
502
+ color: white;
503
+ padding: 2px 6px;
504
+ border-radius: 4px;
505
+ font-size: 10px;
506
+ font-weight: bold;
507
+ }
508
+
279
509
  .window-thumbnail {
280
510
  height: 80px;
281
511
  background: #f5f5f5;
@@ -312,31 +542,169 @@ function showRecordingPreview(windowInfo) {
312
542
  color: #999;
313
543
  font-size: 11px;
314
544
  }
545
+
546
+ /* Real-time info panel */
547
+ #current-window-info {
548
+ position: fixed;
549
+ top: 20px;
550
+ right: 20px;
551
+ background: rgba(0, 0, 0, 0.9);
552
+ color: white;
553
+ padding: 15px;
554
+ border-radius: 8px;
555
+ min-width: 250px;
556
+ opacity: 0;
557
+ transform: translateX(300px);
558
+ transition: all 0.3s ease;
559
+ z-index: 1000;
560
+ font-family: 'Monaco', monospace;
561
+ font-size: 12px;
562
+ }
563
+
564
+ #current-window-info.visible {
565
+ opacity: 1;
566
+ transform: translateX(0);
567
+ }
568
+
569
+ #current-window-info h4 {
570
+ margin: 0 0 10px 0;
571
+ color: #ff6b35;
572
+ font-size: 14px;
573
+ }
574
+
575
+ #current-window-info p {
576
+ margin: 4px 0;
577
+ line-height: 1.4;
578
+ }
579
+
580
+ #current-window-info strong {
581
+ color: #fff;
582
+ }
583
+
584
+ /* Animation for smooth transitions */
585
+ .window-card {
586
+ will-change: transform, box-shadow, border-color;
587
+ }
588
+
589
+ @keyframes pulseHover {
590
+ 0% { transform: scale(1) translateY(-2px); }
591
+ 50% { transform: scale(1.02) translateY(-3px); }
592
+ 100% { transform: scale(1) translateY(-2px); }
593
+ }
594
+
595
+ .window-card.hover-detected {
596
+ animation: pulseHover 2s infinite;
597
+ }
598
+
599
+ /* Status indicator */
600
+ .tracking-status {
601
+ position: absolute;
602
+ top: 10px;
603
+ left: 20px;
604
+ background: #4CAF50;
605
+ color: white;
606
+ padding: 5px 10px;
607
+ border-radius: 15px;
608
+ font-size: 12px;
609
+ font-weight: bold;
610
+ }
611
+
612
+ .tracking-status::before {
613
+ content: "🔴 ";
614
+ animation: blink 1s infinite;
615
+ }
616
+
617
+ @keyframes blink {
618
+ 0%, 50% { opacity: 1; }
619
+ 51%, 100% { opacity: 0; }
620
+ }
315
621
  ```
316
622
 
623
+ ## ✨ Yeni Real-Time Tracking Özellikleri
624
+
625
+ ### 🎯 Anlık Window Tespit
626
+ - **100ms polling** ile smooth mouse tracking
627
+ - Mouse hangi pencere üstüne giderse **otomatik highlight**
628
+ - **Screen detection** - pencere hangi ekranda, otomatik tespit
629
+ - **Koordinat bilgisi** - x, y, width, height gerçek zamanlı
630
+
631
+ ### 🔥 UI Features
632
+ - `hover-detected` class ile anlık görsel feedback
633
+ - Real-time info panel (sağ üst köşe)
634
+ - Pulse animation effect
635
+ - "🎯 CURSOR HERE" indicator
636
+
637
+ ### 🖥️ Multi-Screen Support
638
+ - Pencere hangi screen'de otomatik tespit
639
+ - Screen koordinatları ve boyutları dahil
640
+ - Cross-screen window tracking
641
+
317
642
  ## ⚠️ Önemli Notlar
318
643
 
319
- 1. **Native Overlays**: Electron modunda native NSWindow overlays devre dışıdır
320
- 2. **Auto Selection**: Şu an demo amaçlı otomatik seçim yapıyor, gerçek uygulamada UI ile seçim yapılmalı
321
- 3. **Permission Check**: `checkPermissions()` tüm modlarda çalışır
322
- 4. **Event Handling**: Electron'da event'ler IPC ile main ve renderer process arasında taşınmalı
644
+ ### 🔧 Teknik Gereksinimler
645
+ 1. **Native Module**: Real-time tracking için native binding gerekli
646
+ 2. **macOS Permissions**: Screen Recording ve Accessibility izinleri
647
+ 3. **Electron Environment**: `ELECTRON_VERSION` env variable otomatik tespit
648
+ 4. **Performance**: 100ms polling interval (ayarlanabilir)
649
+
650
+ ### 🛠️ Implementation Notes
651
+ 1. **IPC Communication**: Main ↔ Renderer process real-time data exchange
652
+ 2. **Memory Management**: Interval cleanup önemli
653
+ 3. **Error Handling**: Native binding yoksa graceful fallback
654
+ 4. **UI Responsiveness**: CSS transitions ile smooth UX
655
+
656
+ ### 🚨 Troubleshooting
657
+ - **Native binding yüklenemezse**: `npm run build` ile tekrar derle
658
+ - **Permission hatası**: System Preferences → Security & Privacy
659
+ - **Tracking çalışmıyorsa**: `ELECTRON_VERSION` environment variable kontrol et
660
+ - **UI update yavaşsa**: Polling interval'ı artır (100ms → 200ms)
323
661
 
324
662
  ## 🚀 Sonraki Adımlar
325
663
 
326
- 1. Thumbnail generation implementasyonu
327
- 2. Real-time window list updates
328
- 3. Multiple display support UI
329
- 4. Recording progress indicator
330
- 5. Custom recording area selection
664
+ ### Phase 1: Core Enhancement
665
+ 1. ✅ **Real-time window tracking** - TAMAMLANDI
666
+ 2. **Screen detection accuracy** - TAMAMLANDI
667
+ 3. **Electron compatibility** - TAMAMLANDI
668
+ 4. 🔄 Thumbnail generation implementasyonu
669
+
670
+ ### Phase 2: Advanced Features
671
+ 5. 📋 Window list real-time updates
672
+ 6. 🖥️ Multiple display UI enhancement
673
+ 7. 📹 Recording progress indicator
674
+ 8. ✂️ Custom recording area selection
675
+ 9. 🎨 Window preview thumbnails
676
+
677
+ ### Phase 3: Performance & UX
678
+ 10. ⚡ Performance optimization
679
+ 11. 🎭 Advanced animations
680
+ 12. 📱 Responsive design improvements
681
+ 13. 🔧 Settings panel
331
682
 
332
683
  ## 📞 Test Komutları
333
684
 
334
685
  ```bash
335
- # Electron mode test
336
- ELECTRON_VERSION=25.0.0 node test-electron-window-selector.js
686
+ # Fixed overlay functionality test
687
+ node test-overlay-fix.js
688
+
689
+ # Electron mode test
690
+ ELECTRON_VERSION=25.0.0 node test-overlay-fix.js
691
+
692
+ # Build native module
693
+ npm run build
337
694
 
338
- # Node.js mode test
695
+ # Full integration test
339
696
  node test-electron-window-selector.js
340
697
  ```
341
698
 
342
- Bu implementasyon sayesinde Electron uygulamaları crash olmadan pencere seçimi yapabilir ve recording işlevlerini güvenli şekilde kullanabilir.
699
+ ## 🎉 Sonuç
700
+
701
+ Bu güncellenmiş implementasyon ile:
702
+
703
+ ✅ **Mouse tracking** gerçek zamanlı çalışıyor
704
+ ✅ **Window detection** hassas ve hızlı
705
+ ✅ **Screen coordination** doğru hesaplanıyor
706
+ ✅ **Electron integration** sorunsuz çalışıyor
707
+ ✅ **Multi-display support** tam uyumlu
708
+ ✅ **Real-time UI feedback** kullanıcı dostu
709
+
710
+ Electron uygulamanızda artık native overlay benzeri deneyim sunabilir, kullanıcı mouse'u hareket ettirdikçe hangi pencere üstünde olduğunu görebilir ve tek tıkla recording başlatabilirsiniz!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.4.10",
3
+ "version": "2.4.11",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -497,22 +497,56 @@ NSArray* getAllSelectableWindowsLegacy() {
497
497
  }
498
498
  }
499
499
 
500
- // Get window under cursor point
500
+ // Get window under cursor point using real-time mouse position
501
501
  NSDictionary* getWindowUnderCursor(CGPoint point) {
502
502
  @autoreleasepool {
503
- if (!g_allWindows) return nil;
504
-
505
- // Find window that contains the cursor point
506
- for (NSDictionary *window in g_allWindows) {
507
- int x = [[window objectForKey:@"x"] intValue];
508
- int y = [[window objectForKey:@"y"] intValue];
509
- int width = [[window objectForKey:@"width"] intValue];
510
- int height = [[window objectForKey:@"height"] intValue];
503
+ // Get window ID directly under cursor using macOS API
504
+ CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
505
+
506
+ if (windowList) {
507
+ NSArray *windowArray = (__bridge NSArray *)windowList;
511
508
 
512
- if (point.x >= x && point.x <= x + width &&
513
- point.y >= y && point.y <= y + height) {
514
- return window;
509
+ // Find the topmost window at cursor position
510
+ for (NSDictionary *windowInfo in windowArray) {
511
+ NSDictionary *bounds = [windowInfo objectForKey:(NSString *)kCGWindowBounds];
512
+ NSNumber *layer = [windowInfo objectForKey:(NSString *)kCGWindowLayer];
513
+ NSString *owner = [windowInfo objectForKey:(NSString *)kCGWindowOwnerName];
514
+ NSNumber *winID = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
515
+
516
+ if (!bounds || !winID || !owner) continue;
517
+ if ([layer intValue] != 0) continue; // Only normal windows
518
+ if ([owner isEqualToString:@"WindowServer"] || [owner isEqualToString:@"Dock"]) continue;
519
+
520
+ int x = [[bounds objectForKey:@"X"] intValue];
521
+ int y = [[bounds objectForKey:@"Y"] intValue];
522
+ int width = [[bounds objectForKey:@"Width"] intValue];
523
+ int height = [[bounds objectForKey:@"Height"] intValue];
524
+
525
+ // Skip too small windows
526
+ if (width < 50 || height < 50) continue;
527
+
528
+ // Check if cursor is within window bounds
529
+ if (point.x >= x && point.x <= x + width &&
530
+ point.y >= y && point.y <= y + height) {
531
+
532
+ NSString *windowName = [windowInfo objectForKey:(NSString *)kCGWindowName];
533
+
534
+ // Create window info in our format
535
+ NSDictionary *window = @{
536
+ @"id": winID,
537
+ @"title": windowName ?: @"Untitled",
538
+ @"appName": owner,
539
+ @"x": @(x),
540
+ @"y": @(y),
541
+ @"width": @(width),
542
+ @"height": @(height)
543
+ };
544
+
545
+ CFRelease(windowList);
546
+ return window;
547
+ }
515
548
  }
549
+ CFRelease(windowList);
516
550
  }
517
551
 
518
552
  return nil;
@@ -1274,26 +1308,54 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1274
1308
  NSLog(@" 📊 Details: ID=%@, Pos=(%d,%d), Size=%dx%d",
1275
1309
  [g_selectedWindowInfo objectForKey:@"id"], x, y, width, height);
1276
1310
 
1277
- // Get all screens
1311
+ // Get all screens and find which screen contains this window
1278
1312
  NSArray *screens = [NSScreen screens];
1279
1313
  NSScreen *windowScreen = nil;
1280
1314
  NSScreen *mainScreen = [NSScreen mainScreen];
1281
1315
 
1316
+ // Calculate window center point for better screen detection
1317
+ CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
1318
+
1282
1319
  for (NSScreen *screen in screens) {
1283
1320
  NSRect screenFrame = [screen frame];
1284
1321
 
1285
- // Convert window coordinates to screen-relative
1286
- if (x >= screenFrame.origin.x &&
1287
- x < screenFrame.origin.x + screenFrame.size.width &&
1288
- y >= screenFrame.origin.y &&
1289
- y < screenFrame.origin.y + screenFrame.size.height) {
1322
+ // Check if window center is within screen bounds
1323
+ if (windowCenter.x >= screenFrame.origin.x &&
1324
+ windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
1325
+ windowCenter.y >= screenFrame.origin.y &&
1326
+ windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
1290
1327
  windowScreen = screen;
1328
+ NSLog(@" 🖥️ Window found on screen: (%.0f,%.0f) %.0fx%.0f",
1329
+ screenFrame.origin.x, screenFrame.origin.y,
1330
+ screenFrame.size.width, screenFrame.size.height);
1291
1331
  break;
1292
1332
  }
1293
1333
  }
1294
1334
 
1335
+ // If no exact match, find screen with maximum overlap
1336
+ if (!windowScreen) {
1337
+ CGFloat maxOverlapArea = 0;
1338
+ NSRect windowRect = NSMakeRect(x, y, width, height);
1339
+
1340
+ for (NSScreen *screen in screens) {
1341
+ NSRect screenFrame = [screen frame];
1342
+ NSRect intersection = NSIntersectionRect(windowRect, screenFrame);
1343
+ CGFloat overlapArea = intersection.size.width * intersection.size.height;
1344
+
1345
+ if (overlapArea > maxOverlapArea) {
1346
+ maxOverlapArea = overlapArea;
1347
+ windowScreen = screen;
1348
+ }
1349
+ }
1350
+
1351
+ if (windowScreen) {
1352
+ NSLog(@" 🖥️ Window assigned to screen with max overlap: %.0f pixels²", maxOverlapArea);
1353
+ }
1354
+ }
1355
+
1295
1356
  if (!windowScreen) {
1296
1357
  windowScreen = mainScreen;
1358
+ NSLog(@" 🖥️ Window defaulted to main screen");
1297
1359
  }
1298
1360
 
1299
1361
  // Add screen information
@@ -1361,13 +1423,74 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
1361
1423
  updateOverlay();
1362
1424
  }
1363
1425
 
1426
+ // For Electron mode, also get real-time window under cursor
1427
+ const char* electronVersion = getenv("ELECTRON_VERSION");
1428
+ const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1429
+
1364
1430
  Napi::Object result = Napi::Object::New(env);
1365
1431
  result.Set("isSelecting", Napi::Boolean::New(env, g_isWindowSelecting));
1366
1432
  result.Set("hasSelectedWindow", Napi::Boolean::New(env, g_selectedWindowInfo != nil));
1367
1433
  result.Set("windowCount", Napi::Number::New(env, g_allWindows ? [g_allWindows count] : 0));
1368
1434
  result.Set("hasOverlay", Napi::Boolean::New(env, g_overlayWindow != nil));
1369
1435
 
1370
- if (g_currentWindowUnderCursor) {
1436
+ if (electronVersion || electronRunAs) {
1437
+ // In Electron mode, get real-time window under cursor
1438
+ @try {
1439
+ NSPoint mouseLocation = [NSEvent mouseLocation];
1440
+ NSScreen *mainScreen = [NSScreen mainScreen];
1441
+ CGFloat screenHeight = [mainScreen frame].size.height;
1442
+ CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
1443
+
1444
+ NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
1445
+
1446
+ if (windowUnderCursor) {
1447
+ Napi::Object currentWindow = Napi::Object::New(env);
1448
+ currentWindow.Set("id", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"id"] intValue]));
1449
+ currentWindow.Set("title", Napi::String::New(env, [[windowUnderCursor objectForKey:@"title"] UTF8String]));
1450
+ currentWindow.Set("appName", Napi::String::New(env, [[windowUnderCursor objectForKey:@"appName"] UTF8String]));
1451
+ currentWindow.Set("x", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"x"] intValue]));
1452
+ currentWindow.Set("y", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"y"] intValue]));
1453
+ currentWindow.Set("width", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"width"] intValue]));
1454
+ currentWindow.Set("height", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"height"] intValue]));
1455
+
1456
+ // Add screen detection for Electron
1457
+ int x = [[windowUnderCursor objectForKey:@"x"] intValue];
1458
+ int y = [[windowUnderCursor objectForKey:@"y"] intValue];
1459
+ int width = [[windowUnderCursor objectForKey:@"width"] intValue];
1460
+ int height = [[windowUnderCursor objectForKey:@"height"] intValue];
1461
+
1462
+ NSArray *screens = [NSScreen screens];
1463
+ NSScreen *windowScreen = nil;
1464
+ CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
1465
+
1466
+ for (NSScreen *screen in screens) {
1467
+ NSRect screenFrame = [screen frame];
1468
+ if (windowCenter.x >= screenFrame.origin.x &&
1469
+ windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
1470
+ windowCenter.y >= screenFrame.origin.y &&
1471
+ windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
1472
+ windowScreen = screen;
1473
+ break;
1474
+ }
1475
+ }
1476
+
1477
+ if (windowScreen) {
1478
+ NSRect screenFrame = [windowScreen frame];
1479
+ currentWindow.Set("screenId", Napi::Number::New(env, [[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] ?
1480
+ [[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue] : 0));
1481
+ currentWindow.Set("screenX", Napi::Number::New(env, (int)screenFrame.origin.x));
1482
+ currentWindow.Set("screenY", Napi::Number::New(env, (int)screenFrame.origin.y));
1483
+ currentWindow.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
1484
+ currentWindow.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1485
+ }
1486
+
1487
+ result.Set("currentWindow", currentWindow);
1488
+ }
1489
+ } @catch (NSException *exception) {
1490
+ // Ignore mouse tracking errors in Electron mode
1491
+ }
1492
+ } else if (g_currentWindowUnderCursor) {
1493
+ // Native mode
1371
1494
  Napi::Object currentWindow = Napi::Object::New(env);
1372
1495
  currentWindow.Set("id", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"id"] intValue]));
1373
1496
  currentWindow.Set("title", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"title"] UTF8String]));
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ const ElectronWindowSelector = require('./electron-window-selector.js');
4
+
5
+ console.log('🧪 Testing Fixed Overlay Functionality');
6
+ console.log('=====================================');
7
+
8
+ async function testOverlayFunctionality() {
9
+ const selector = new ElectronWindowSelector();
10
+
11
+ try {
12
+ console.log('\n🔍 Environment Check:');
13
+ const status = selector.getStatus();
14
+ console.log(` - Electron Mode: ${status.isElectron}`);
15
+
16
+ console.log('\n🪟 Testing Window Detection...');
17
+
18
+ // Test real-time window detection
19
+ console.log('Move your mouse over different windows...');
20
+ console.log('Press Ctrl+C to stop\n');
21
+
22
+ let lastWindowId = null;
23
+
24
+ const checkInterval = setInterval(async () => {
25
+ try {
26
+ // Simulate what Electron app would do - poll for current window
27
+ const windowStatus = require('./build/Release/mac_recorder.node').getWindowSelectionStatus();
28
+
29
+ if (windowStatus && windowStatus.currentWindow) {
30
+ const window = windowStatus.currentWindow;
31
+
32
+ if (window.id !== lastWindowId) {
33
+ lastWindowId = window.id;
34
+
35
+ console.log(`🎯 Window Detected: ${window.appName} - "${window.title}"`);
36
+ console.log(` 📍 Position: (${window.x}, ${window.y})`);
37
+ console.log(` 📏 Size: ${window.width}x${window.height}`);
38
+
39
+ if (window.screenId !== undefined) {
40
+ console.log(` 🖥️ Screen: ${window.screenId} (${window.screenWidth}x${window.screenHeight})`);
41
+ }
42
+ console.log('');
43
+ }
44
+ } else if (lastWindowId !== null) {
45
+ lastWindowId = null;
46
+ console.log('🚪 No window under cursor\n');
47
+ }
48
+ } catch (error) {
49
+ console.error('Error during window detection:', error.message);
50
+ }
51
+ }, 100); // Check every 100ms for smooth tracking
52
+
53
+ // Handle Ctrl+C gracefully
54
+ process.on('SIGINT', () => {
55
+ console.log('\n\n🛑 Stopping test...');
56
+ clearInterval(checkInterval);
57
+ selector.cleanup().then(() => {
58
+ console.log('✅ Cleanup completed');
59
+ process.exit(0);
60
+ });
61
+ });
62
+
63
+ } catch (error) {
64
+ console.error('❌ Test failed:', error.message);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ // Set Electron environment for testing
70
+ process.env.ELECTRON_VERSION = '25.0.0';
71
+
72
+ testOverlayFunctionality();