node-mac-recorder 1.6.0 → 1.8.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 +11 -6
- package/index.js +7 -1
- package/package.json +1 -1
- package/src/window_selector.mm +75 -23
- package/window-selector.js +40 -5
|
@@ -8,15 +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
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)
|
|
18
|
+
- **Screen Selection**: Tam ekran overlay ile ekran seçimi (menu bar dahil, ESC ile iptal)
|
|
19
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
|
|
20
21
|
- **Permission Management**: macOS izin kontrolü ve yönetimi
|
|
21
22
|
|
|
22
23
|
## 🚀 Kurulum
|
|
@@ -59,7 +60,7 @@ async function selectWindow() {
|
|
|
59
60
|
const selector = new WindowSelector();
|
|
60
61
|
|
|
61
62
|
try {
|
|
62
|
-
console.log('Bir pencere seçin...');
|
|
63
|
+
console.log('Bir pencere seçin (ESC ile iptal)...');
|
|
63
64
|
const selectedWindow = await selector.selectWindow();
|
|
64
65
|
|
|
65
66
|
console.log('Seçilen pencere:', {
|
|
@@ -594,7 +595,7 @@ async function selectScreen() {
|
|
|
594
595
|
const selector = new WindowSelector();
|
|
595
596
|
|
|
596
597
|
try {
|
|
597
|
-
console.log('Bir ekran seçin...');
|
|
598
|
+
console.log('Bir ekran seçin (ESC ile iptal)...');
|
|
598
599
|
const selectedScreen = await selector.selectScreen();
|
|
599
600
|
|
|
600
601
|
console.log('Seçilen ekran:', {
|
|
@@ -607,7 +608,11 @@ async function selectScreen() {
|
|
|
607
608
|
return selectedScreen;
|
|
608
609
|
|
|
609
610
|
} catch (error) {
|
|
610
|
-
|
|
611
|
+
if (error.message.includes('cancelled')) {
|
|
612
|
+
console.log('❌ Seçim iptal edildi');
|
|
613
|
+
} else {
|
|
614
|
+
console.error('Hata:', error.message);
|
|
615
|
+
}
|
|
611
616
|
} finally {
|
|
612
617
|
await selector.cleanup();
|
|
613
618
|
}
|
|
@@ -624,7 +629,7 @@ async function manualScreenSelection() {
|
|
|
624
629
|
try {
|
|
625
630
|
// Ekran seçimini başlat
|
|
626
631
|
await selector.startScreenSelection();
|
|
627
|
-
console.log('🖥️ Screen overlays shown - click
|
|
632
|
+
console.log('🖥️ Screen overlays shown - click Start Record button (ESC to cancel)');
|
|
628
633
|
|
|
629
634
|
// Polling ile seçim bekle
|
|
630
635
|
const checkSelection = () => {
|
package/index.js
CHANGED
|
@@ -167,7 +167,13 @@ class MacRecorder extends EventEmitter {
|
|
|
167
167
|
targetDisplayId = display.id; // Use actual display ID, not array index
|
|
168
168
|
// Koordinatları display'e göre normalize et
|
|
169
169
|
adjustedX = targetWindow.x - display.x;
|
|
170
|
-
|
|
170
|
+
|
|
171
|
+
// Y coordinate fix: Window selector uses NSScreen.frame (excludes menu bar)
|
|
172
|
+
// but recording uses CGDisplayBounds (includes menu bar)
|
|
173
|
+
// For primary display, add menu bar height (~22-25px) to Y coordinate
|
|
174
|
+
const isMainDisplay = display.isPrimary;
|
|
175
|
+
const menuBarHeight = isMainDisplay ? 25 : 0; // Menu bar height adjustment
|
|
176
|
+
adjustedY = targetWindow.y - display.y + menuBarHeight;
|
|
171
177
|
break;
|
|
172
178
|
}
|
|
173
179
|
}
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -27,6 +27,7 @@ static bool g_isScreenSelecting = false;
|
|
|
27
27
|
static NSMutableArray *g_screenOverlayWindows = nil;
|
|
28
28
|
static NSDictionary *g_selectedScreenInfo = nil;
|
|
29
29
|
static NSArray *g_allScreens = nil;
|
|
30
|
+
static id g_screenKeyEventMonitor = nil;
|
|
30
31
|
|
|
31
32
|
// Forward declarations
|
|
32
33
|
void cleanupWindowSelector();
|
|
@@ -139,13 +140,15 @@ bool hideScreenRecordingPreview();
|
|
|
139
140
|
|
|
140
141
|
NSRect windowRect = NSMakeRect(windowX, convertedY, windowWidth, windowHeight);
|
|
141
142
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
145
148
|
|
|
146
|
-
//
|
|
147
|
-
[[NSColor
|
|
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];
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
@end
|
|
@@ -222,14 +225,19 @@ bool hideScreenRecordingPreview();
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
- (void)screenSelectButtonClicked:(id)sender {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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();
|
|
233
241
|
}
|
|
234
242
|
}
|
|
235
243
|
|
|
@@ -637,6 +645,12 @@ bool hideRecordingPreview() {
|
|
|
637
645
|
void cleanupScreenSelector() {
|
|
638
646
|
g_isScreenSelecting = false;
|
|
639
647
|
|
|
648
|
+
// Remove key event monitor
|
|
649
|
+
if (g_screenKeyEventMonitor) {
|
|
650
|
+
[NSEvent removeMonitor:g_screenKeyEventMonitor];
|
|
651
|
+
g_screenKeyEventMonitor = nil;
|
|
652
|
+
}
|
|
653
|
+
|
|
640
654
|
// Close all screen overlay windows
|
|
641
655
|
if (g_screenOverlayWindows) {
|
|
642
656
|
for (NSWindow *overlayWindow in g_screenOverlayWindows) {
|
|
@@ -704,11 +718,26 @@ bool startScreenSelection() {
|
|
|
704
718
|
|
|
705
719
|
// Create select button
|
|
706
720
|
NSButton *selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 180, 60)];
|
|
707
|
-
[selectButton setTitle:@"
|
|
721
|
+
[selectButton setTitle:@"Start Record"];
|
|
708
722
|
[selectButton setButtonType:NSButtonTypeMomentaryPushIn];
|
|
709
723
|
[selectButton setBezelStyle:NSBezelStyleRounded];
|
|
710
724
|
[selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
|
|
711
|
-
[selectButton
|
|
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];
|
|
712
741
|
|
|
713
742
|
// Add shadow for better visibility
|
|
714
743
|
[selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
|
|
@@ -716,9 +745,11 @@ bool startScreenSelection() {
|
|
|
716
745
|
[selectButton.layer setShadowRadius:4.0];
|
|
717
746
|
[selectButton.layer setShadowOpacity:0.3];
|
|
718
747
|
|
|
719
|
-
// Set button target and action
|
|
720
|
-
|
|
721
|
-
|
|
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];
|
|
722
753
|
[selectButton setAction:@selector(screenSelectButtonClicked:)];
|
|
723
754
|
|
|
724
755
|
// Position button in center of screen
|
|
@@ -740,7 +771,16 @@ bool startScreenSelection() {
|
|
|
740
771
|
[screenInfoArray release];
|
|
741
772
|
g_isScreenSelecting = true;
|
|
742
773
|
|
|
743
|
-
|
|
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]);
|
|
744
784
|
|
|
745
785
|
return true;
|
|
746
786
|
|
|
@@ -875,13 +915,25 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
875
915
|
|
|
876
916
|
// Create select button with blue theme
|
|
877
917
|
g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 160, 60)];
|
|
878
|
-
[g_selectButton setTitle:@"
|
|
918
|
+
[g_selectButton setTitle:@"Start Record"];
|
|
879
919
|
[g_selectButton setButtonType:NSButtonTypeMomentaryPushIn];
|
|
880
920
|
[g_selectButton setBezelStyle:NSBezelStyleRounded];
|
|
881
921
|
[g_selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
|
|
882
922
|
|
|
883
|
-
//
|
|
884
|
-
[g_selectButton setWantsLayer:
|
|
923
|
+
// Blue background with white text
|
|
924
|
+
[g_selectButton setWantsLayer:YES];
|
|
925
|
+
[g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
|
|
926
|
+
[g_selectButton.layer setCornerRadius:8.0];
|
|
927
|
+
[g_selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
|
|
928
|
+
[g_selectButton.layer setBorderWidth:2.0];
|
|
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];
|
|
885
937
|
|
|
886
938
|
// Add shadow for better visibility
|
|
887
939
|
[g_selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
|
package/window-selector.js
CHANGED
|
@@ -312,6 +312,9 @@ class WindowSelector extends EventEmitter {
|
|
|
312
312
|
async startScreenSelection() {
|
|
313
313
|
try {
|
|
314
314
|
const success = nativeBinding.startScreenSelection();
|
|
315
|
+
if (success) {
|
|
316
|
+
this._isScreenSelecting = true;
|
|
317
|
+
}
|
|
315
318
|
return success;
|
|
316
319
|
} catch (error) {
|
|
317
320
|
throw new Error(`Failed to start screen selection: ${error.message}`);
|
|
@@ -325,6 +328,7 @@ class WindowSelector extends EventEmitter {
|
|
|
325
328
|
async stopScreenSelection() {
|
|
326
329
|
try {
|
|
327
330
|
const success = nativeBinding.stopScreenSelection();
|
|
331
|
+
this._isScreenSelecting = false;
|
|
328
332
|
return success;
|
|
329
333
|
} catch (error) {
|
|
330
334
|
throw new Error(`Failed to stop screen selection: ${error.message}`);
|
|
@@ -337,13 +341,32 @@ class WindowSelector extends EventEmitter {
|
|
|
337
341
|
*/
|
|
338
342
|
getSelectedScreen() {
|
|
339
343
|
try {
|
|
340
|
-
|
|
344
|
+
const selectedScreen = nativeBinding.getSelectedScreenInfo();
|
|
345
|
+
if (selectedScreen) {
|
|
346
|
+
// Screen selected, update status
|
|
347
|
+
this._isScreenSelecting = false;
|
|
348
|
+
}
|
|
349
|
+
return selectedScreen;
|
|
341
350
|
} catch (error) {
|
|
342
351
|
console.error(`Failed to get selected screen: ${error.message}`);
|
|
343
352
|
return null;
|
|
344
353
|
}
|
|
345
354
|
}
|
|
346
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
|
+
|
|
347
370
|
/**
|
|
348
371
|
* Promise tabanlı ekran seçimi
|
|
349
372
|
* Kullanıcı bir ekran seçene kadar bekler
|
|
@@ -356,13 +379,22 @@ class WindowSelector extends EventEmitter {
|
|
|
356
379
|
|
|
357
380
|
// Poll for selection completion
|
|
358
381
|
return new Promise((resolve, reject) => {
|
|
382
|
+
let isResolved = false;
|
|
383
|
+
|
|
359
384
|
const checkSelection = () => {
|
|
385
|
+
if (isResolved) return; // Prevent multiple resolutions
|
|
386
|
+
|
|
360
387
|
const selectedScreen = this.getSelectedScreen();
|
|
361
388
|
if (selectedScreen) {
|
|
389
|
+
isResolved = true;
|
|
362
390
|
resolve(selectedScreen);
|
|
363
|
-
} else {
|
|
364
|
-
//
|
|
391
|
+
} else if (this.isScreenSelecting) {
|
|
392
|
+
// Still selecting, check again
|
|
365
393
|
setTimeout(checkSelection, 100);
|
|
394
|
+
} else {
|
|
395
|
+
// Selection was cancelled (probably ESC key)
|
|
396
|
+
isResolved = true;
|
|
397
|
+
reject(new Error('Screen selection was cancelled'));
|
|
366
398
|
}
|
|
367
399
|
};
|
|
368
400
|
|
|
@@ -371,8 +403,11 @@ class WindowSelector extends EventEmitter {
|
|
|
371
403
|
|
|
372
404
|
// Timeout after 60 seconds
|
|
373
405
|
setTimeout(() => {
|
|
374
|
-
|
|
375
|
-
|
|
406
|
+
if (!isResolved) {
|
|
407
|
+
isResolved = true;
|
|
408
|
+
this.stopScreenSelection();
|
|
409
|
+
reject(new Error('Screen selection timed out'));
|
|
410
|
+
}
|
|
376
411
|
}, 60000);
|
|
377
412
|
});
|
|
378
413
|
} catch (error) {
|