node-mac-recorder 1.5.0 → 1.6.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.
@@ -14,6 +14,9 @@ Bu modül, macOS'ta sistem imleci ile pencere seçimi yapabilmenizi sağlayan g
14
14
  - **Event-driven API**: Pencere hover, seçim ve hata durumları için event'ler
15
15
  - **Window Focus Control**: Detect edilen pencereyi otomatik olarak en öne getirir
16
16
  - **Auto Bring-to-Front**: Cursor hangi pencereye gelirse otomatik focus yapar
17
+ - **Recording Preview Overlay**: Kayıt alanını görselleştiren tam ekran overlay sistemi
18
+ - **Screen Selection**: Tam ekran overlay ile ekran seçimi (menu bar dahil)
19
+ - **Screen Recording Preview**: Seçilen ekran için kayıt önizleme sistemi
17
20
  - **Permission Management**: macOS izin kontrolü ve yönetimi
18
21
 
19
22
  ## 🚀 Kurulum
@@ -175,6 +178,84 @@ selector.setBringToFrontEnabled(true); // Auto mode ON
175
178
  selector.setBringToFrontEnabled(false); // Auto mode OFF
176
179
  ```
177
180
 
181
+ ##### `async showRecordingPreview(windowInfo)`
182
+ Seçilen pencere için kayıt önizleme overlay'ini gösterir. Tüm ekranı siyah yapar, sadece pencere alanını şeffaf bırakır.
183
+
184
+ **Parameters:**
185
+ - `windowInfo` (WindowInfo) - Pencere bilgileri
186
+
187
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
188
+
189
+ ```javascript
190
+ const success = await selector.showRecordingPreview(selectedWindow);
191
+ ```
192
+
193
+ ##### `async hideRecordingPreview()`
194
+ Kayıt önizleme overlay'ini gizler.
195
+
196
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
197
+
198
+ ```javascript
199
+ const success = await selector.hideRecordingPreview();
200
+ ```
201
+
202
+ ##### `async startScreenSelection()`
203
+ Ekran seçim modunu başlatır. Tüm ekranları overlay ile gösterir.
204
+
205
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
206
+
207
+ ```javascript
208
+ const success = await selector.startScreenSelection();
209
+ ```
210
+
211
+ ##### `async stopScreenSelection()`
212
+ Ekran seçim modunu durdurur.
213
+
214
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
215
+
216
+ ```javascript
217
+ const success = await selector.stopScreenSelection();
218
+ ```
219
+
220
+ ##### `getSelectedScreen()`
221
+ Son seçilen ekran bilgisini döndürür.
222
+
223
+ **Returns:** `ScreenInfo | null`
224
+
225
+ ```javascript
226
+ const screenInfo = selector.getSelectedScreen();
227
+ ```
228
+
229
+ ##### `async selectScreen()`
230
+ Promise tabanlı ekran seçimi. Kullanıcı bir ekran seçene kadar bekler.
231
+
232
+ **Returns:** `Promise<ScreenInfo>`
233
+
234
+ ```javascript
235
+ const selectedScreen = await selector.selectScreen();
236
+ ```
237
+
238
+ ##### `async showScreenRecordingPreview(screenInfo)`
239
+ Seçilen ekran için kayıt önizleme overlay'ini gösterir. Diğer ekranları siyah yapar, sadece seçili ekranı şeffaf bırakır.
240
+
241
+ **Parameters:**
242
+ - `screenInfo` (ScreenInfo) - Ekran bilgileri
243
+
244
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
245
+
246
+ ```javascript
247
+ const success = await selector.showScreenRecordingPreview(selectedScreen);
248
+ ```
249
+
250
+ ##### `async hideScreenRecordingPreview()`
251
+ Ekran kayıt önizleme overlay'ini gizler.
252
+
253
+ **Returns:** `Promise<boolean>` - Başarı/başarısızlık
254
+
255
+ ```javascript
256
+ const success = await selector.hideScreenRecordingPreview();
257
+ ```
258
+
178
259
  ##### `async cleanup()`
179
260
  Tüm kaynakları temizler ve seçimi durdurur.
180
261
 
@@ -267,6 +348,20 @@ selector.on('error', (error) => {
267
348
  }
268
349
  ```
269
350
 
351
+ ### ScreenInfo
352
+ ```javascript
353
+ {
354
+ id: number, // Ekran ID'si (0, 1, 2, ...)
355
+ name: string, // Ekran adı ("Display 1", "Display 2", ...)
356
+ x: number, // Global X pozisyonu
357
+ y: number, // Global Y pozisyonu
358
+ width: number, // Ekran genişliği
359
+ height: number, // Ekran yüksekliği
360
+ resolution: string, // Çözünürlük string'i ("1920x1080")
361
+ isPrimary: boolean // Ana ekran mı?
362
+ }
363
+ ```
364
+
270
365
  ## 🎮 Test Etme
271
366
 
272
367
  ### Test Dosyasını Çalıştır
@@ -298,6 +393,7 @@ node examples/window-selector-example.js --help
298
393
 
299
394
  ## ⚡ Nasıl Çalışır?
300
395
 
396
+ ### Pencere Seçim Süreci
301
397
  1. **Window Detection**: macOS `CGWindowListCopyWindowInfo` API'si ile açık pencereleri tespit eder
302
398
  2. **Cursor Tracking**: Real-time olarak imleç pozisyonunu takip eder
303
399
  3. **Overlay Rendering**: NSWindow ile transparant overlay penceresi oluşturur
@@ -306,6 +402,29 @@ node examples/window-selector-example.js --help
306
402
  6. **User Interaction**: Merkeze yerleştirilen button ile seçim yapar
307
403
  7. **Data Collection**: Seçilen pencerenin tüm bilgilerini toplar
308
404
 
405
+ ### Kayıt Önizleme Sistemi (Pencere)
406
+ 1. **Full Screen Overlay**: Tüm ekranı kaplayan siyah transparan katman oluşturur
407
+ 2. **Window Cutout**: Seçilen pencere alanını şeffaf hale getirir (cut-out effect)
408
+ 3. **Coordinate Conversion**: CGWindow koordinatlarını NSView koordinatlarına dönüştürür
409
+ 4. **Multi-Display Support**: Çoklu ekran kurulumlarında doğru pozisyonlama yapar
410
+ 5. **Non-Interactive**: Mouse events'leri geçirir, kullanıcı etkileşimini engellemeZ
411
+ 6. **Clean Management**: Programatik açma/kapama kontrolü sağlar
412
+
413
+ ### Ekran Seçim Sistemi
414
+ 1. **Multi-Screen Detection**: NSScreen.screens ile tüm ekranları tespit eder
415
+ 2. **Full Screen Coverage**: Her ekran için tam kaplama overlay oluşturur (menu bar dahil)
416
+ 3. **Interactive Overlays**: Her ekranda merkezi "Select Screen" butonu
417
+ 4. **Screen Information Display**: Ekran adı ve çözünürlük bilgilerini gösterir
418
+ 5. **Automatic Assignment**: Her overlay'i kendi ekranına otomatik atar
419
+ 6. **Selection Feedback**: Seçim yapıldığında anında geri bildirim
420
+
421
+ ### Ekran Kayıt Önizleme Sistemi
422
+ 1. **Multi-Screen Management**: Birden fazla ekranı aynı anda yönetir
423
+ 2. **Selective Darkening**: Sadece seçilmeyen ekranları siyah overlay ile kaplar
424
+ 3. **Recording Area Highlight**: Seçilen ekran tamamen şeffaf kalır
425
+ 4. **Screen-Specific Overlays**: Her ekran için ayrı overlay penceresi
426
+ 5. **Coordinate Independence**: Her ekranın kendi koordinat sistemini kullanır
427
+
309
428
  ## 🔧 Troubleshooting
310
429
 
311
430
  ### Build Hataları
@@ -375,7 +494,157 @@ async function manualFocus() {
375
494
  }
376
495
  ```
377
496
 
378
- ### Otomatik Pencere Kaydı
497
+ ### Ekran Seçimi ile Kayıt
498
+ ```javascript
499
+ const WindowSelector = require('./window-selector');
500
+ const MacRecorder = require('./index');
501
+
502
+ async function recordScreenWithPreview() {
503
+ const selector = new WindowSelector();
504
+ const recorder = new MacRecorder();
505
+
506
+ try {
507
+ // Ekran seç
508
+ const screen = await selector.selectScreen();
509
+ console.log(`Selected: ${screen.name} (${screen.resolution})`);
510
+
511
+ // Kayıt önizlemesi göster (diğer ekranlar siyah, seçili ekran şeffaf)
512
+ await selector.showScreenRecordingPreview(screen);
513
+ console.log('🎬 Screen recording preview shown');
514
+
515
+ // 3 saniye bekle
516
+ await new Promise(resolve => setTimeout(resolve, 3000));
517
+
518
+ // Ekran kaydını başlat
519
+ const outputPath = `./recordings/screen-${screen.id}-${Date.now()}.mov`;
520
+ await recorder.startRecording(outputPath, {
521
+ displayId: screen.id,
522
+ captureCursor: true,
523
+ includeMicrophone: true
524
+ });
525
+
526
+ console.log('🔴 Screen recording started...');
527
+
528
+ // 10 saniye kaydet
529
+ setTimeout(async () => {
530
+ await recorder.stopRecording();
531
+
532
+ // Önizleme overlay'ini gizle
533
+ await selector.hideScreenRecordingPreview();
534
+ console.log(`✅ Recording saved: ${outputPath}`);
535
+ }, 10000);
536
+
537
+ } finally {
538
+ await selector.cleanup();
539
+ }
540
+ }
541
+ ```
542
+
543
+ ### Kayıt Önizleme ile Pencere Kaydı
544
+ ```javascript
545
+ const WindowSelector = require('./window-selector');
546
+ const MacRecorder = require('./index');
547
+
548
+ async function recordWithPreview() {
549
+ const selector = new WindowSelector();
550
+ const recorder = new MacRecorder();
551
+
552
+ try {
553
+ // Pencere seç
554
+ const window = await selector.selectWindow();
555
+ console.log(`Selected: ${window.title}`);
556
+
557
+ // Kayıt önizlemesi göster (siyah overlay + şeffaf pencere alanı)
558
+ await selector.showRecordingPreview(window);
559
+ console.log('🎬 Recording preview shown - you can see exact recording area');
560
+
561
+ // 3 saniye bekle (kullanıcı görebilsin)
562
+ await new Promise(resolve => setTimeout(resolve, 3000));
563
+
564
+ // Kaydı başlat
565
+ const outputPath = `./recordings/${window.appName}-${Date.now()}.mov`;
566
+ await recorder.startRecording(outputPath, {
567
+ windowId: window.id,
568
+ captureCursor: true,
569
+ includeMicrophone: true
570
+ });
571
+
572
+ console.log('🔴 Recording started...');
573
+
574
+ // 10 saniye kaydet
575
+ setTimeout(async () => {
576
+ await recorder.stopRecording();
577
+
578
+ // Önizleme overlay'ini gizle
579
+ await selector.hideRecordingPreview();
580
+ console.log(`✅ Recording saved: ${outputPath}`);
581
+ }, 10000);
582
+
583
+ } finally {
584
+ await selector.cleanup();
585
+ }
586
+ }
587
+ ```
588
+
589
+ ### Basit Ekran Seçimi
590
+ ```javascript
591
+ const WindowSelector = require('./window-selector');
592
+
593
+ async function selectScreen() {
594
+ const selector = new WindowSelector();
595
+
596
+ try {
597
+ console.log('Bir ekran seçin...');
598
+ const selectedScreen = await selector.selectScreen();
599
+
600
+ console.log('Seçilen ekran:', {
601
+ name: selectedScreen.name,
602
+ resolution: selectedScreen.resolution,
603
+ position: `(${selectedScreen.x}, ${selectedScreen.y})`,
604
+ isPrimary: selectedScreen.isPrimary
605
+ });
606
+
607
+ return selectedScreen;
608
+
609
+ } catch (error) {
610
+ console.error('Hata:', error.message);
611
+ } finally {
612
+ await selector.cleanup();
613
+ }
614
+ }
615
+ ```
616
+
617
+ ### Manuel Ekran Kontrolü
618
+ ```javascript
619
+ const WindowSelector = require('./window-selector');
620
+
621
+ async function manualScreenSelection() {
622
+ const selector = new WindowSelector();
623
+
624
+ try {
625
+ // Ekran seçimini başlat
626
+ await selector.startScreenSelection();
627
+ console.log('🖥️ Screen overlays shown - click Select Screen button');
628
+
629
+ // Polling ile seçim bekle
630
+ const checkSelection = () => {
631
+ const selected = selector.getSelectedScreen();
632
+ if (selected) {
633
+ console.log(`✅ Screen selected: ${selected.name}`);
634
+ return selected;
635
+ }
636
+ setTimeout(checkSelection, 100);
637
+ };
638
+
639
+ checkSelection();
640
+
641
+ } catch (error) {
642
+ console.error('Hata:', error.message);
643
+ }
644
+ }
645
+ ```
646
+
647
+ ### Otomatik Pencere Kaydı (Basit)
379
648
  ```javascript
380
649
  const WindowSelector = require('./window-selector');
381
650
  const MacRecorder = require('./index');
@@ -450,6 +719,7 @@ Bu modül ana projenin lisansı altındadır.
450
719
 
451
720
  ## ⭐ Özellik İstekleri
452
721
 
722
+ ### Pencere Seçimi
453
723
  - [ ] Pencere gruplandırma
454
724
  - [ ] Hotkey desteği
455
725
  - [ ] Pencere filtreleme
@@ -457,6 +727,14 @@ Bu modül ana projenin lisansı altındadır.
457
727
  - [ ] Screenshot alma
458
728
  - [ ] Window history
459
729
 
730
+ ### Ekran Seçimi
731
+ - [x] Tam ekran overlay (menu bar dahil) ✅
732
+ - [x] Multi-display desteği ✅
733
+ - [x] Kayıt önizleme sistemi ✅
734
+ - [ ] Hotkey desteği
735
+ - [ ] Çoklu ekran seçimi
736
+ - [ ] Ekran thumbnail'ları
737
+
460
738
  ---
461
739
 
462
740
  **Not**: Bu modül sadece macOS'ta çalışır ve sistem izinleri gerektirir.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -17,12 +17,32 @@ static NSMutableArray *g_allWindows = nil;
17
17
  static NSDictionary *g_currentWindowUnderCursor = nil;
18
18
  static bool g_bringToFrontEnabled = true; // Default enabled
19
19
 
20
+ // Recording preview overlay state
21
+ static NSWindow *g_recordingPreviewWindow = nil;
22
+ static NSView *g_recordingPreviewView = nil;
23
+ static NSDictionary *g_recordingWindowInfo = nil;
24
+
25
+ // Screen selection overlay state
26
+ static bool g_isScreenSelecting = false;
27
+ static NSMutableArray *g_screenOverlayWindows = nil;
28
+ static NSDictionary *g_selectedScreenInfo = nil;
29
+ static NSArray *g_allScreens = nil;
30
+
20
31
  // Forward declarations
21
32
  void cleanupWindowSelector();
22
33
  void updateOverlay();
23
34
  NSDictionary* getWindowUnderCursor(CGPoint point);
24
35
  NSArray* getAllSelectableWindows();
25
36
  bool bringWindowToFront(int windowId);
37
+ void cleanupRecordingPreview();
38
+ bool showRecordingPreview(NSDictionary *windowInfo);
39
+ bool hideRecordingPreview();
40
+ void cleanupScreenSelector();
41
+ bool startScreenSelection();
42
+ bool stopScreenSelection();
43
+ NSDictionary* getSelectedScreenInfo();
44
+ bool showScreenRecordingPreview(NSDictionary *screenInfo);
45
+ bool hideScreenRecordingPreview();
26
46
 
27
47
  // Custom overlay view class
28
48
  @interface WindowSelectorOverlayView : NSView
@@ -35,7 +55,7 @@ bool bringWindowToFront(int windowId);
35
55
  self = [super initWithFrame:frameRect];
36
56
  if (self) {
37
57
  self.wantsLayer = YES;
38
- self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.6] CGColor];
58
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] CGColor];
39
59
  self.layer.borderColor = [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor];
40
60
  self.layer.borderWidth = 5.0;
41
61
  self.layer.cornerRadius = 8.0;
@@ -49,7 +69,7 @@ bool bringWindowToFront(int windowId);
49
69
  if (!self.windowInfo) return;
50
70
 
51
71
  // Background with transparency
52
- [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.6] setFill];
72
+ [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] setFill];
53
73
  NSRectFill(dirtyRect);
54
74
 
55
75
  // Border
@@ -80,9 +100,116 @@ bool bringWindowToFront(int windowId);
80
100
 
81
101
  @end
82
102
 
103
+ // Recording preview overlay view - full screen with cutout
104
+ @interface RecordingPreviewView : NSView
105
+ @property (nonatomic, strong) NSDictionary *recordingWindowInfo;
106
+ @end
107
+
108
+ @implementation RecordingPreviewView
109
+
110
+ - (instancetype)initWithFrame:(NSRect)frameRect {
111
+ self = [super initWithFrame:frameRect];
112
+ if (self) {
113
+ self.wantsLayer = YES;
114
+ self.layer.backgroundColor = [[NSColor clearColor] CGColor];
115
+ }
116
+ return self;
117
+ }
118
+
119
+ - (void)drawRect:(NSRect)dirtyRect {
120
+ [super drawRect:dirtyRect];
121
+
122
+ if (!self.recordingWindowInfo) {
123
+ // No window info, fill with semi-transparent black
124
+ [[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5] setFill];
125
+ NSRectFill(dirtyRect);
126
+ return;
127
+ }
128
+
129
+ // Get window coordinates
130
+ int windowX = [[self.recordingWindowInfo objectForKey:@"x"] intValue];
131
+ int windowY = [[self.recordingWindowInfo objectForKey:@"y"] intValue];
132
+ int windowWidth = [[self.recordingWindowInfo objectForKey:@"width"] intValue];
133
+ int windowHeight = [[self.recordingWindowInfo objectForKey:@"height"] intValue];
134
+
135
+ // Convert from CGWindow coordinates (top-left) to NSView coordinates (bottom-left)
136
+ NSScreen *mainScreen = [NSScreen mainScreen];
137
+ CGFloat screenHeight = [mainScreen frame].size.height;
138
+ CGFloat convertedY = screenHeight - windowY - windowHeight;
139
+
140
+ NSRect windowRect = NSMakeRect(windowX, convertedY, windowWidth, windowHeight);
141
+
142
+ // Fill entire view with semi-transparent black
143
+ [[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5] setFill];
144
+ NSRectFill(self.bounds);
145
+
146
+ // Cut out the window area (make it transparent)
147
+ [[NSColor clearColor] setFill];
148
+ NSRectFill(windowRect);
149
+ }
150
+
151
+ @end
152
+
153
+ // Screen selection overlay view
154
+ @interface ScreenSelectorOverlayView : NSView
155
+ @property (nonatomic, strong) NSDictionary *screenInfo;
156
+ @end
157
+
158
+ @implementation ScreenSelectorOverlayView
159
+
160
+ - (instancetype)initWithFrame:(NSRect)frameRect {
161
+ self = [super initWithFrame:frameRect];
162
+ if (self) {
163
+ self.wantsLayer = YES;
164
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] CGColor];
165
+ self.layer.borderColor = [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor];
166
+ self.layer.borderWidth = 5.0;
167
+ self.layer.cornerRadius = 8.0;
168
+ }
169
+ return self;
170
+ }
171
+
172
+ - (void)drawRect:(NSRect)dirtyRect {
173
+ [super drawRect:dirtyRect];
174
+
175
+ if (!self.screenInfo) return;
176
+
177
+ // Background with transparency
178
+ [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] setFill];
179
+ NSRectFill(dirtyRect);
180
+
181
+ // Border
182
+ [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] setStroke];
183
+ NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:8 yRadius:8];
184
+ [border setLineWidth:3.0];
185
+ [border stroke];
186
+
187
+ // Screen info text
188
+ NSString *screenName = [self.screenInfo objectForKey:@"name"] ?: @"Unknown Screen";
189
+ NSString *resolution = [self.screenInfo objectForKey:@"resolution"] ?: @"Unknown Resolution";
190
+ NSString *infoText = [NSString stringWithFormat:@"%@\n%@", screenName, resolution];
191
+
192
+ NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
193
+ [style setAlignment:NSTextAlignmentCenter];
194
+
195
+ NSDictionary *attributes = @{
196
+ NSFontAttributeName: [NSFont systemFontOfSize:21 weight:NSFontWeightMedium],
197
+ NSForegroundColorAttributeName: [NSColor whiteColor],
198
+ NSParagraphStyleAttributeName: style,
199
+ NSStrokeColorAttributeName: [NSColor blackColor],
200
+ NSStrokeWidthAttributeName: @(-2.0)
201
+ };
202
+
203
+ NSRect textRect = NSMakeRect(10, self.bounds.size.height - 90, self.bounds.size.width - 20, 80);
204
+ [infoText drawInRect:textRect withAttributes:attributes];
205
+ }
206
+
207
+ @end
208
+
83
209
  // Button action handler and timer target
84
210
  @interface WindowSelectorDelegate : NSObject
85
211
  - (void)selectButtonClicked:(id)sender;
212
+ - (void)screenSelectButtonClicked:(id)sender;
86
213
  - (void)timerUpdate:(NSTimer *)timer;
87
214
  @end
88
215
 
@@ -94,6 +221,18 @@ bool bringWindowToFront(int windowId);
94
221
  }
95
222
  }
96
223
 
224
+ - (void)screenSelectButtonClicked:(id)sender {
225
+ // Get the screen info from the button's superview
226
+ NSView *overlayView = [[sender superview] superview];
227
+ if ([overlayView isKindOfClass:[ScreenSelectorOverlayView class]]) {
228
+ ScreenSelectorOverlayView *screenView = (ScreenSelectorOverlayView *)overlayView;
229
+ if (screenView.screenInfo) {
230
+ g_selectedScreenInfo = [screenView.screenInfo retain];
231
+ cleanupScreenSelector();
232
+ }
233
+ }
234
+ }
235
+
97
236
  - (void)timerUpdate:(NSTimer *)timer {
98
237
  updateOverlay();
99
238
  }
@@ -313,7 +452,14 @@ void updateOverlay() {
313
452
  CGFloat screenHeight = [mainScreen frame].size.height;
314
453
  CGFloat adjustedY = screenHeight - y - height;
315
454
 
316
- NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
455
+ // Clamp overlay to screen bounds to avoid partial off-screen issues
456
+ NSRect screenFrame = [mainScreen frame];
457
+ CGFloat clampedX = MAX(screenFrame.origin.x, MIN(x, screenFrame.origin.x + screenFrame.size.width - width));
458
+ CGFloat clampedY = MAX(screenFrame.origin.y, MIN(adjustedY, screenFrame.origin.y + screenFrame.size.height - height));
459
+ CGFloat clampedWidth = MIN(width, screenFrame.size.width - (clampedX - screenFrame.origin.x));
460
+ CGFloat clampedHeight = MIN(height, screenFrame.size.height - (clampedY - screenFrame.origin.y));
461
+
462
+ NSRect overlayFrame = NSMakeRect(clampedX, clampedY, clampedWidth, clampedHeight);
317
463
 
318
464
  NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
319
465
  NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
@@ -407,6 +553,288 @@ void cleanupWindowSelector() {
407
553
  }
408
554
  }
409
555
 
556
+ // Recording preview functions
557
+ void cleanupRecordingPreview() {
558
+ if (g_recordingPreviewWindow) {
559
+ [g_recordingPreviewWindow close];
560
+ g_recordingPreviewWindow = nil;
561
+ g_recordingPreviewView = nil;
562
+ }
563
+
564
+ if (g_recordingWindowInfo) {
565
+ [g_recordingWindowInfo release];
566
+ g_recordingWindowInfo = nil;
567
+ }
568
+ }
569
+
570
+ bool showRecordingPreview(NSDictionary *windowInfo) {
571
+ @try {
572
+ // Clean up any existing preview
573
+ cleanupRecordingPreview();
574
+
575
+ if (!windowInfo) return false;
576
+
577
+ // Store window info
578
+ g_recordingWindowInfo = [windowInfo retain];
579
+
580
+ // Get main screen bounds for full screen overlay
581
+ NSScreen *mainScreen = [NSScreen mainScreen];
582
+ NSRect screenFrame = [mainScreen frame];
583
+
584
+ // Create full-screen overlay window
585
+ g_recordingPreviewWindow = [[NSWindow alloc] initWithContentRect:screenFrame
586
+ styleMask:NSWindowStyleMaskBorderless
587
+ backing:NSBackingStoreBuffered
588
+ defer:NO];
589
+
590
+ [g_recordingPreviewWindow setLevel:CGWindowLevelForKey(kCGOverlayWindowLevelKey)]; // High level but below selection
591
+ [g_recordingPreviewWindow setOpaque:NO];
592
+ [g_recordingPreviewWindow setBackgroundColor:[NSColor clearColor]];
593
+ [g_recordingPreviewWindow setIgnoresMouseEvents:YES]; // Don't interfere with user interaction
594
+ [g_recordingPreviewWindow setAcceptsMouseMovedEvents:NO];
595
+ [g_recordingPreviewWindow setHasShadow:NO];
596
+ [g_recordingPreviewWindow setAlphaValue:1.0];
597
+ [g_recordingPreviewWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
598
+
599
+ // Create preview view
600
+ g_recordingPreviewView = [[RecordingPreviewView alloc] initWithFrame:screenFrame];
601
+ [(RecordingPreviewView *)g_recordingPreviewView setRecordingWindowInfo:windowInfo];
602
+ [g_recordingPreviewWindow setContentView:g_recordingPreviewView];
603
+
604
+ // Show the preview
605
+ [g_recordingPreviewWindow orderFront:nil];
606
+ [g_recordingPreviewWindow makeKeyAndOrderFront:nil];
607
+
608
+ NSLog(@"🎬 RECORDING PREVIEW: Showing overlay for %@ - \"%@\"",
609
+ [windowInfo objectForKey:@"appName"],
610
+ [windowInfo objectForKey:@"title"]);
611
+
612
+ return true;
613
+
614
+ } @catch (NSException *exception) {
615
+ NSLog(@"❌ Error showing recording preview: %@", exception);
616
+ cleanupRecordingPreview();
617
+ return false;
618
+ }
619
+ }
620
+
621
+ bool hideRecordingPreview() {
622
+ @try {
623
+ if (g_recordingPreviewWindow) {
624
+ NSLog(@"🎬 RECORDING PREVIEW: Hiding overlay");
625
+ cleanupRecordingPreview();
626
+ return true;
627
+ }
628
+ return false;
629
+
630
+ } @catch (NSException *exception) {
631
+ NSLog(@"❌ Error hiding recording preview: %@", exception);
632
+ return false;
633
+ }
634
+ }
635
+
636
+ // Screen selection functions
637
+ void cleanupScreenSelector() {
638
+ g_isScreenSelecting = false;
639
+
640
+ // Close all screen overlay windows
641
+ if (g_screenOverlayWindows) {
642
+ for (NSWindow *overlayWindow in g_screenOverlayWindows) {
643
+ [overlayWindow close];
644
+ }
645
+ [g_screenOverlayWindows release];
646
+ g_screenOverlayWindows = nil;
647
+ }
648
+
649
+ // Clean up screen data
650
+ if (g_allScreens) {
651
+ [g_allScreens release];
652
+ g_allScreens = nil;
653
+ }
654
+ }
655
+
656
+ bool startScreenSelection() {
657
+ @try {
658
+ if (g_isScreenSelecting) return false;
659
+
660
+ // Get all available screens
661
+ NSArray *screens = [NSScreen screens];
662
+ if (!screens || [screens count] == 0) return false;
663
+
664
+ // Create screen info array
665
+ NSMutableArray *screenInfoArray = [[NSMutableArray alloc] init];
666
+ g_screenOverlayWindows = [[NSMutableArray alloc] init];
667
+
668
+ for (NSInteger i = 0; i < [screens count]; i++) {
669
+ NSScreen *screen = [screens objectAtIndex:i];
670
+ NSRect screenFrame = [screen frame];
671
+
672
+ // Create screen info dictionary
673
+ NSMutableDictionary *screenInfo = [[NSMutableDictionary alloc] init];
674
+ [screenInfo setObject:[NSNumber numberWithInteger:i] forKey:@"id"];
675
+ [screenInfo setObject:[NSString stringWithFormat:@"Display %ld", (long)(i + 1)] forKey:@"name"];
676
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.origin.x] forKey:@"x"];
677
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.origin.y] forKey:@"y"];
678
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.size.width] forKey:@"width"];
679
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.size.height] forKey:@"height"];
680
+ [screenInfo setObject:[NSString stringWithFormat:@"%.0fx%.0f", screenFrame.size.width, screenFrame.size.height] forKey:@"resolution"];
681
+ [screenInfo setObject:[NSNumber numberWithBool:(i == 0)] forKey:@"isPrimary"]; // First screen is primary
682
+ [screenInfoArray addObject:screenInfo];
683
+
684
+ // Create overlay window for this screen (FULL screen including menu bar)
685
+ NSWindow *overlayWindow = [[NSWindow alloc] initWithContentRect:screenFrame
686
+ styleMask:NSWindowStyleMaskBorderless
687
+ backing:NSBackingStoreBuffered
688
+ defer:NO
689
+ screen:screen];
690
+
691
+ [overlayWindow setLevel:CGWindowLevelForKey(kCGMaximumWindowLevelKey)];
692
+ [overlayWindow setOpaque:NO];
693
+ [overlayWindow setBackgroundColor:[NSColor clearColor]];
694
+ [overlayWindow setIgnoresMouseEvents:NO];
695
+ [overlayWindow setAcceptsMouseMovedEvents:YES];
696
+ [overlayWindow setHasShadow:NO];
697
+ [overlayWindow setAlphaValue:1.0];
698
+ [overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
699
+
700
+ // Create overlay view
701
+ ScreenSelectorOverlayView *overlayView = [[ScreenSelectorOverlayView alloc] initWithFrame:screenFrame];
702
+ [overlayView setScreenInfo:screenInfo];
703
+ [overlayWindow setContentView:overlayView];
704
+
705
+ // Create select button
706
+ NSButton *selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 180, 60)];
707
+ [selectButton setTitle:@"Select Screen"];
708
+ [selectButton setButtonType:NSButtonTypeMomentaryPushIn];
709
+ [selectButton setBezelStyle:NSBezelStyleRounded];
710
+ [selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
711
+ [selectButton setWantsLayer:NO]; // Use system default
712
+
713
+ // Add shadow for better visibility
714
+ [selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
715
+ [selectButton.layer setShadowOffset:NSMakeSize(0, -2)];
716
+ [selectButton.layer setShadowRadius:4.0];
717
+ [selectButton.layer setShadowOpacity:0.3];
718
+
719
+ // Set button target and action
720
+ WindowSelectorDelegate *delegate = [[WindowSelectorDelegate alloc] init];
721
+ [selectButton setTarget:delegate];
722
+ [selectButton setAction:@selector(screenSelectButtonClicked:)];
723
+
724
+ // Position button in center of screen
725
+ NSPoint buttonCenter = NSMakePoint(
726
+ (screenFrame.size.width - [selectButton frame].size.width) / 2,
727
+ (screenFrame.size.height - [selectButton frame].size.height) / 2
728
+ );
729
+ [selectButton setFrameOrigin:buttonCenter];
730
+
731
+ [overlayView addSubview:selectButton];
732
+ [overlayWindow orderFront:nil];
733
+ [overlayWindow makeKeyAndOrderFront:nil];
734
+
735
+ [g_screenOverlayWindows addObject:overlayWindow];
736
+ [screenInfo release];
737
+ }
738
+
739
+ g_allScreens = [screenInfoArray retain];
740
+ [screenInfoArray release];
741
+ g_isScreenSelecting = true;
742
+
743
+ NSLog(@"🖥️ SCREEN SELECTION: Started with %lu screens", (unsigned long)[screens count]);
744
+
745
+ return true;
746
+
747
+ } @catch (NSException *exception) {
748
+ NSLog(@"❌ Error starting screen selection: %@", exception);
749
+ cleanupScreenSelector();
750
+ return false;
751
+ }
752
+ }
753
+
754
+ bool stopScreenSelection() {
755
+ @try {
756
+ if (!g_isScreenSelecting) return false;
757
+
758
+ cleanupScreenSelector();
759
+ NSLog(@"🖥️ SCREEN SELECTION: Stopped");
760
+ return true;
761
+
762
+ } @catch (NSException *exception) {
763
+ NSLog(@"❌ Error stopping screen selection: %@", exception);
764
+ return false;
765
+ }
766
+ }
767
+
768
+ NSDictionary* getSelectedScreenInfo() {
769
+ if (!g_selectedScreenInfo) return nil;
770
+
771
+ NSDictionary *result = [g_selectedScreenInfo retain];
772
+ [g_selectedScreenInfo release];
773
+ g_selectedScreenInfo = nil;
774
+
775
+ return [result autorelease];
776
+ }
777
+
778
+ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
779
+ @try {
780
+ // Clean up any existing preview
781
+ cleanupRecordingPreview();
782
+
783
+ if (!screenInfo) return false;
784
+
785
+ // For screen recording preview, we show all OTHER screens as black overlay
786
+ // and keep the selected screen transparent
787
+ NSArray *screens = [NSScreen screens];
788
+ if (!screens || [screens count] == 0) return false;
789
+
790
+ int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
791
+
792
+ // Create overlay for each screen except the selected one
793
+ for (NSInteger i = 0; i < [screens count]; i++) {
794
+ if (i == selectedScreenId) continue; // Skip selected screen
795
+
796
+ NSScreen *screen = [screens objectAtIndex:i];
797
+ NSRect screenFrame = [screen frame];
798
+
799
+ // Create full-screen black overlay for non-selected screens
800
+ NSWindow *overlayWindow = [[NSWindow alloc] initWithContentRect:screenFrame
801
+ styleMask:NSWindowStyleMaskBorderless
802
+ backing:NSBackingStoreBuffered
803
+ defer:NO
804
+ screen:screen];
805
+
806
+ [overlayWindow setLevel:CGWindowLevelForKey(kCGOverlayWindowLevelKey)];
807
+ [overlayWindow setOpaque:NO];
808
+ [overlayWindow setBackgroundColor:[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5]];
809
+ [overlayWindow setIgnoresMouseEvents:YES];
810
+ [overlayWindow setAcceptsMouseMovedEvents:NO];
811
+ [overlayWindow setHasShadow:NO];
812
+ [overlayWindow setAlphaValue:1.0];
813
+ [overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
814
+
815
+ [overlayWindow orderFront:nil];
816
+ [overlayWindow makeKeyAndOrderFront:nil];
817
+
818
+ // Store for cleanup (reuse recording preview window variable)
819
+ if (!g_recordingPreviewWindow) {
820
+ g_recordingPreviewWindow = overlayWindow;
821
+ }
822
+ }
823
+
824
+ NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %d", selectedScreenId);
825
+
826
+ return true;
827
+
828
+ } @catch (NSException *exception) {
829
+ NSLog(@"❌ Error showing screen recording preview: %@", exception);
830
+ return false;
831
+ }
832
+ }
833
+
834
+ bool hideScreenRecordingPreview() {
835
+ return hideRecordingPreview(); // Reuse existing function
836
+ }
837
+
410
838
  // NAPI Function: Start Window Selection
411
839
  Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
412
840
  Napi::Env env = info.Env();
@@ -446,19 +874,14 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
446
874
  [g_overlayWindow setContentView:g_overlayView];
447
875
 
448
876
  // Create select button with blue theme
449
- g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 140, 50)];
877
+ g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 160, 60)];
450
878
  [g_selectButton setTitle:@"Select Window"];
451
879
  [g_selectButton setButtonType:NSButtonTypeMomentaryPushIn];
452
880
  [g_selectButton setBezelStyle:NSBezelStyleRounded];
453
881
  [g_selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
454
882
 
455
- // Blue themed button styling
456
- [g_selectButton setWantsLayer:YES];
457
- [g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
458
- [g_selectButton.layer setCornerRadius:8.0];
459
- [g_selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
460
- [g_selectButton.layer setBorderWidth:2.0];
461
- [g_selectButton setContentTintColor:[NSColor whiteColor]];
883
+ // Remove custom button styling to use system default
884
+ [g_selectButton setWantsLayer:NO];
462
885
 
463
886
  // Add shadow for better visibility
464
887
  [g_selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
@@ -641,6 +1064,193 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
641
1064
  return result;
642
1065
  }
643
1066
 
1067
+ // NAPI Function: Show Recording Preview
1068
+ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1069
+ Napi::Env env = info.Env();
1070
+
1071
+ if (info.Length() < 1) {
1072
+ Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1073
+ return env.Null();
1074
+ }
1075
+
1076
+ if (!info[0].IsObject()) {
1077
+ Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1078
+ return env.Null();
1079
+ }
1080
+
1081
+ @try {
1082
+ Napi::Object windowInfoObj = info[0].As<Napi::Object>();
1083
+
1084
+ // Convert NAPI object to NSDictionary
1085
+ NSMutableDictionary *windowInfo = [[NSMutableDictionary alloc] init];
1086
+
1087
+ if (windowInfoObj.Has("id")) {
1088
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("id").As<Napi::Number>().Int32Value()] forKey:@"id"];
1089
+ }
1090
+ if (windowInfoObj.Has("title")) {
1091
+ [windowInfo setObject:[NSString stringWithUTF8String:windowInfoObj.Get("title").As<Napi::String>().Utf8Value().c_str()] forKey:@"title"];
1092
+ }
1093
+ if (windowInfoObj.Has("appName")) {
1094
+ [windowInfo setObject:[NSString stringWithUTF8String:windowInfoObj.Get("appName").As<Napi::String>().Utf8Value().c_str()] forKey:@"appName"];
1095
+ }
1096
+ if (windowInfoObj.Has("x")) {
1097
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("x").As<Napi::Number>().Int32Value()] forKey:@"x"];
1098
+ }
1099
+ if (windowInfoObj.Has("y")) {
1100
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("y").As<Napi::Number>().Int32Value()] forKey:@"y"];
1101
+ }
1102
+ if (windowInfoObj.Has("width")) {
1103
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("width").As<Napi::Number>().Int32Value()] forKey:@"width"];
1104
+ }
1105
+ if (windowInfoObj.Has("height")) {
1106
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("height").As<Napi::Number>().Int32Value()] forKey:@"height"];
1107
+ }
1108
+
1109
+ bool success = showRecordingPreview(windowInfo);
1110
+ [windowInfo release];
1111
+
1112
+ return Napi::Boolean::New(env, success);
1113
+
1114
+ } @catch (NSException *exception) {
1115
+ return Napi::Boolean::New(env, false);
1116
+ }
1117
+ }
1118
+
1119
+ // NAPI Function: Hide Recording Preview
1120
+ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
1121
+ Napi::Env env = info.Env();
1122
+
1123
+ @try {
1124
+ bool success = hideRecordingPreview();
1125
+ return Napi::Boolean::New(env, success);
1126
+
1127
+ } @catch (NSException *exception) {
1128
+ return Napi::Boolean::New(env, false);
1129
+ }
1130
+ }
1131
+
1132
+ // NAPI Function: Start Screen Selection
1133
+ Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
1134
+ Napi::Env env = info.Env();
1135
+
1136
+ @try {
1137
+ bool success = startScreenSelection();
1138
+ return Napi::Boolean::New(env, success);
1139
+
1140
+ } @catch (NSException *exception) {
1141
+ return Napi::Boolean::New(env, false);
1142
+ }
1143
+ }
1144
+
1145
+ // NAPI Function: Stop Screen Selection
1146
+ Napi::Value StopScreenSelection(const Napi::CallbackInfo& info) {
1147
+ Napi::Env env = info.Env();
1148
+
1149
+ @try {
1150
+ bool success = stopScreenSelection();
1151
+ return Napi::Boolean::New(env, success);
1152
+
1153
+ } @catch (NSException *exception) {
1154
+ return Napi::Boolean::New(env, false);
1155
+ }
1156
+ }
1157
+
1158
+ // NAPI Function: Get Selected Screen Info
1159
+ Napi::Value GetSelectedScreenInfo(const Napi::CallbackInfo& info) {
1160
+ Napi::Env env = info.Env();
1161
+
1162
+ @try {
1163
+ NSDictionary *screenInfo = getSelectedScreenInfo();
1164
+ if (!screenInfo) {
1165
+ return env.Null();
1166
+ }
1167
+
1168
+ Napi::Object result = Napi::Object::New(env);
1169
+ result.Set("id", Napi::Number::New(env, [[screenInfo objectForKey:@"id"] intValue]));
1170
+ result.Set("name", Napi::String::New(env, [[screenInfo objectForKey:@"name"] UTF8String]));
1171
+ result.Set("x", Napi::Number::New(env, [[screenInfo objectForKey:@"x"] intValue]));
1172
+ result.Set("y", Napi::Number::New(env, [[screenInfo objectForKey:@"y"] intValue]));
1173
+ result.Set("width", Napi::Number::New(env, [[screenInfo objectForKey:@"width"] intValue]));
1174
+ result.Set("height", Napi::Number::New(env, [[screenInfo objectForKey:@"height"] intValue]));
1175
+ result.Set("resolution", Napi::String::New(env, [[screenInfo objectForKey:@"resolution"] UTF8String]));
1176
+ result.Set("isPrimary", Napi::Boolean::New(env, [[screenInfo objectForKey:@"isPrimary"] boolValue]));
1177
+
1178
+ NSLog(@"🖥️ SCREEN SELECTED: %@ (%@)",
1179
+ [screenInfo objectForKey:@"name"],
1180
+ [screenInfo objectForKey:@"resolution"]);
1181
+
1182
+ return result;
1183
+
1184
+ } @catch (NSException *exception) {
1185
+ return env.Null();
1186
+ }
1187
+ }
1188
+
1189
+ // NAPI Function: Show Screen Recording Preview
1190
+ Napi::Value ShowScreenRecordingPreview(const Napi::CallbackInfo& info) {
1191
+ Napi::Env env = info.Env();
1192
+
1193
+ if (info.Length() < 1) {
1194
+ Napi::TypeError::New(env, "Screen info object required").ThrowAsJavaScriptException();
1195
+ return env.Null();
1196
+ }
1197
+
1198
+ if (!info[0].IsObject()) {
1199
+ Napi::TypeError::New(env, "Screen info must be an object").ThrowAsJavaScriptException();
1200
+ return env.Null();
1201
+ }
1202
+
1203
+ @try {
1204
+ Napi::Object screenInfoObj = info[0].As<Napi::Object>();
1205
+
1206
+ // Convert NAPI object to NSDictionary
1207
+ NSMutableDictionary *screenInfo = [[NSMutableDictionary alloc] init];
1208
+
1209
+ if (screenInfoObj.Has("id")) {
1210
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("id").As<Napi::Number>().Int32Value()] forKey:@"id"];
1211
+ }
1212
+ if (screenInfoObj.Has("name")) {
1213
+ [screenInfo setObject:[NSString stringWithUTF8String:screenInfoObj.Get("name").As<Napi::String>().Utf8Value().c_str()] forKey:@"name"];
1214
+ }
1215
+ if (screenInfoObj.Has("resolution")) {
1216
+ [screenInfo setObject:[NSString stringWithUTF8String:screenInfoObj.Get("resolution").As<Napi::String>().Utf8Value().c_str()] forKey:@"resolution"];
1217
+ }
1218
+ if (screenInfoObj.Has("x")) {
1219
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("x").As<Napi::Number>().Int32Value()] forKey:@"x"];
1220
+ }
1221
+ if (screenInfoObj.Has("y")) {
1222
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("y").As<Napi::Number>().Int32Value()] forKey:@"y"];
1223
+ }
1224
+ if (screenInfoObj.Has("width")) {
1225
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("width").As<Napi::Number>().Int32Value()] forKey:@"width"];
1226
+ }
1227
+ if (screenInfoObj.Has("height")) {
1228
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("height").As<Napi::Number>().Int32Value()] forKey:@"height"];
1229
+ }
1230
+
1231
+ bool success = showScreenRecordingPreview(screenInfo);
1232
+ [screenInfo release];
1233
+
1234
+ return Napi::Boolean::New(env, success);
1235
+
1236
+ } @catch (NSException *exception) {
1237
+ return Napi::Boolean::New(env, false);
1238
+ }
1239
+ }
1240
+
1241
+ // NAPI Function: Hide Screen Recording Preview
1242
+ Napi::Value HideScreenRecordingPreview(const Napi::CallbackInfo& info) {
1243
+ Napi::Env env = info.Env();
1244
+
1245
+ @try {
1246
+ bool success = hideScreenRecordingPreview();
1247
+ return Napi::Boolean::New(env, success);
1248
+
1249
+ } @catch (NSException *exception) {
1250
+ return Napi::Boolean::New(env, false);
1251
+ }
1252
+ }
1253
+
644
1254
  // Export functions
645
1255
  Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
646
1256
  exports.Set("startWindowSelection", Napi::Function::New(env, StartWindowSelection));
@@ -649,6 +1259,13 @@ Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
649
1259
  exports.Set("getWindowSelectionStatus", Napi::Function::New(env, GetWindowSelectionStatus));
650
1260
  exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
651
1261
  exports.Set("setBringToFrontEnabled", Napi::Function::New(env, SetBringToFrontEnabled));
1262
+ exports.Set("showRecordingPreview", Napi::Function::New(env, ShowRecordingPreview));
1263
+ exports.Set("hideRecordingPreview", Napi::Function::New(env, HideRecordingPreview));
1264
+ exports.Set("startScreenSelection", Napi::Function::New(env, StartScreenSelection));
1265
+ exports.Set("stopScreenSelection", Napi::Function::New(env, StopScreenSelection));
1266
+ exports.Set("getSelectedScreenInfo", Napi::Function::New(env, GetSelectedScreenInfo));
1267
+ exports.Set("showScreenRecordingPreview", Napi::Function::New(env, ShowScreenRecordingPreview));
1268
+ exports.Set("hideScreenRecordingPreview", Napi::Function::New(env, HideScreenRecordingPreview));
652
1269
 
653
1270
  return exports;
654
1271
  }
@@ -272,6 +272,146 @@ class WindowSelector extends EventEmitter {
272
272
  this.isSelecting = false;
273
273
  }
274
274
 
275
+ /**
276
+ * Seçilen pencere için kayıt önizleme overlay'ini gösterir
277
+ * Tüm ekranı siyah yapar, sadece pencere alanını şeffaf bırakır
278
+ * @param {Object} windowInfo - Pencere bilgileri
279
+ * @returns {Promise<boolean>} Success/failure
280
+ */
281
+ async showRecordingPreview(windowInfo) {
282
+ if (!windowInfo) {
283
+ throw new Error("Window info is required");
284
+ }
285
+
286
+ try {
287
+ const success = nativeBinding.showRecordingPreview(windowInfo);
288
+ return success;
289
+ } catch (error) {
290
+ throw new Error(`Failed to show recording preview: ${error.message}`);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Kayıt önizleme overlay'ini gizler
296
+ * @returns {Promise<boolean>} Success/failure
297
+ */
298
+ async hideRecordingPreview() {
299
+ try {
300
+ const success = nativeBinding.hideRecordingPreview();
301
+ return success;
302
+ } catch (error) {
303
+ throw new Error(`Failed to hide recording preview: ${error.message}`);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Ekran seçimi başlatır
309
+ * Tüm ekranları overlay ile gösterir ve seçim yapılmasını bekler
310
+ * @returns {Promise<boolean>} Success/failure
311
+ */
312
+ async startScreenSelection() {
313
+ try {
314
+ const success = nativeBinding.startScreenSelection();
315
+ return success;
316
+ } catch (error) {
317
+ throw new Error(`Failed to start screen selection: ${error.message}`);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Ekran seçimini durdurur
323
+ * @returns {Promise<boolean>} Success/failure
324
+ */
325
+ async stopScreenSelection() {
326
+ try {
327
+ const success = nativeBinding.stopScreenSelection();
328
+ return success;
329
+ } catch (error) {
330
+ throw new Error(`Failed to stop screen selection: ${error.message}`);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Seçilen ekran bilgisini döndürür
336
+ * @returns {Object|null} Screen info or null
337
+ */
338
+ getSelectedScreen() {
339
+ try {
340
+ return nativeBinding.getSelectedScreenInfo();
341
+ } catch (error) {
342
+ console.error(`Failed to get selected screen: ${error.message}`);
343
+ return null;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Promise tabanlı ekran seçimi
349
+ * Kullanıcı bir ekran seçene kadar bekler
350
+ * @returns {Promise<Object>} Selected screen info
351
+ */
352
+ async selectScreen() {
353
+ try {
354
+ // Start screen selection
355
+ await this.startScreenSelection();
356
+
357
+ // Poll for selection completion
358
+ return new Promise((resolve, reject) => {
359
+ const checkSelection = () => {
360
+ const selectedScreen = this.getSelectedScreen();
361
+ if (selectedScreen) {
362
+ resolve(selectedScreen);
363
+ } else {
364
+ // Check again in 100ms
365
+ setTimeout(checkSelection, 100);
366
+ }
367
+ };
368
+
369
+ // Start polling
370
+ checkSelection();
371
+
372
+ // Timeout after 60 seconds
373
+ setTimeout(() => {
374
+ this.stopScreenSelection();
375
+ reject(new Error('Screen selection timed out'));
376
+ }, 60000);
377
+ });
378
+ } catch (error) {
379
+ throw new Error(`Failed to select screen: ${error.message}`);
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Seçilen ekran için kayıt önizleme overlay'ini gösterir
385
+ * Diğer ekranları siyah yapar, sadece seçili ekranı şeffaf bırakır
386
+ * @param {Object} screenInfo - Ekran bilgileri
387
+ * @returns {Promise<boolean>} Success/failure
388
+ */
389
+ async showScreenRecordingPreview(screenInfo) {
390
+ if (!screenInfo) {
391
+ throw new Error("Screen info is required");
392
+ }
393
+
394
+ try {
395
+ const success = nativeBinding.showScreenRecordingPreview(screenInfo);
396
+ return success;
397
+ } catch (error) {
398
+ throw new Error(`Failed to show screen recording preview: ${error.message}`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Ekran kayıt önizleme overlay'ini gizler
404
+ * @returns {Promise<boolean>} Success/failure
405
+ */
406
+ async hideScreenRecordingPreview() {
407
+ try {
408
+ const success = nativeBinding.hideScreenRecordingPreview();
409
+ return success;
410
+ } catch (error) {
411
+ throw new Error(`Failed to hide screen recording preview: ${error.message}`);
412
+ }
413
+ }
414
+
275
415
  /**
276
416
  * macOS'ta pencere seçim izinlerini kontrol eder
277
417
  */