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.
@@ -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 "Select Window" butonu ile kolay seçim
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
- console.error('Hata:', error.message);
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 Select Screen button');
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
- adjustedY = targetWindow.y - display.y;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- // 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);
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
- // Cut out the window area (make it transparent)
147
- [[NSColor clearColor] setFill];
148
- NSRectFill(windowRect);
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
- // 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
- }
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:@"Select Screen"];
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 setWantsLayer:NO]; // Use system default
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
- WindowSelectorDelegate *delegate = [[WindowSelectorDelegate alloc] init];
721
- [selectButton setTarget:delegate];
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
- NSLog(@"🖥️ SCREEN SELECTION: Started with %lu screens", (unsigned long)[screens count]);
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:@"Select Window"];
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
- // Remove custom button styling to use system default
884
- [g_selectButton setWantsLayer:NO];
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]];
@@ -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
- return nativeBinding.getSelectedScreenInfo();
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
- // Check again in 100ms
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
- this.stopScreenSelection();
375
- reject(new Error('Screen selection timed out'));
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) {