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