node-mac-recorder 2.4.10 → 2.4.12

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.
@@ -1,27 +1,19 @@
1
1
  const { EventEmitter } = require("events");
2
2
  const path = require("path");
3
3
 
4
- // Native modülü yükle (arm64 prebuild öncelikli)
4
+ // Native modülü yükle
5
5
  let nativeBinding;
6
6
  try {
7
- if (process.platform === "darwin" && process.arch === "arm64") {
8
- nativeBinding = require("./prebuilds/darwin-arm64/node.napi.node");
9
- } else {
10
- nativeBinding = require("./build/Release/mac_recorder.node");
11
- }
7
+ nativeBinding = require("./build/Release/mac_recorder.node");
12
8
  } catch (error) {
13
9
  try {
14
- nativeBinding = require("./build/Release/mac_recorder.node");
15
- } catch (_) {
16
- try {
17
- nativeBinding = require("./build/Debug/mac_recorder.node");
18
- } catch (debugError) {
19
- throw new Error(
20
- 'Native module not found. Please run "npm run build" to compile the native module.\n' +
21
- "Original error: " +
22
- error.message
23
- );
24
- }
10
+ nativeBinding = require("./build/Debug/mac_recorder.node");
11
+ } catch (debugError) {
12
+ throw new Error(
13
+ 'Native module not found. Please run "npm run build" to compile the native module.\n' +
14
+ "Original error: " +
15
+ error.message
16
+ );
25
17
  }
26
18
  }
27
19
 
@@ -32,15 +24,6 @@ class WindowSelector extends EventEmitter {
32
24
  this.selectionTimer = null;
33
25
  this.selectedWindow = null;
34
26
  this.lastStatus = null;
35
-
36
- // Electron environment detection
37
- this.isElectron = !!(process.versions && process.versions.electron) ||
38
- !!(process.env.ELECTRON_VERSION) ||
39
- !!(process.env.ELECTRON_RUN_AS_NODE);
40
-
41
- if (this.isElectron) {
42
- console.log("🔍 WindowSelector: Detected Electron environment - using safe mode");
43
- }
44
27
  }
45
28
 
46
29
  /**
@@ -57,11 +40,11 @@ class WindowSelector extends EventEmitter {
57
40
  try {
58
41
  // Native window selection başlat
59
42
  const success = nativeBinding.startWindowSelection();
60
-
43
+
61
44
  if (success) {
62
45
  this.isSelecting = true;
63
46
  this.selectedWindow = null;
64
-
47
+
65
48
  // Status polling timer başlat (higher frequency for overlay updates)
66
49
  this.selectionTimer = setInterval(() => {
67
50
  this.checkSelectionStatus();
@@ -89,7 +72,7 @@ class WindowSelector extends EventEmitter {
89
72
  return new Promise((resolve, reject) => {
90
73
  try {
91
74
  const success = nativeBinding.stopWindowSelection();
92
-
75
+
93
76
  // Timer'ı durdur
94
77
  if (this.selectionTimer) {
95
78
  clearInterval(this.selectionTimer);
@@ -115,14 +98,14 @@ class WindowSelector extends EventEmitter {
115
98
 
116
99
  try {
117
100
  const status = nativeBinding.getWindowSelectionStatus();
118
-
101
+
119
102
  // Seçim tamamlandı mı kontrol et
120
103
  if (status.hasSelectedWindow && !this.selectedWindow) {
121
104
  const windowInfo = nativeBinding.getSelectedWindowInfo();
122
105
  if (windowInfo) {
123
106
  this.selectedWindow = windowInfo;
124
107
  this.isSelecting = false;
125
-
108
+
126
109
  // Timer'ı durdur
127
110
  if (this.selectionTimer) {
128
111
  clearInterval(this.selectionTimer);
@@ -138,20 +121,17 @@ class WindowSelector extends EventEmitter {
138
121
  if (this.lastStatus) {
139
122
  const lastWindow = this.lastStatus.currentWindow;
140
123
  const currentWindow = status.currentWindow;
141
-
124
+
142
125
  if (!lastWindow && currentWindow) {
143
126
  // Yeni pencere üstüne gelindi
144
127
  this.emit("windowEntered", currentWindow);
145
128
  } else if (lastWindow && !currentWindow) {
146
129
  // Pencere üstünden ayrıldı
147
130
  this.emit("windowLeft", lastWindow);
148
- } else if (
149
- lastWindow &&
150
- currentWindow &&
151
- (lastWindow.id !== currentWindow.id ||
152
- lastWindow.title !== currentWindow.title ||
153
- lastWindow.appName !== currentWindow.appName)
154
- ) {
131
+ } else if (lastWindow && currentWindow &&
132
+ (lastWindow.id !== currentWindow.id ||
133
+ lastWindow.title !== currentWindow.title ||
134
+ lastWindow.appName !== currentWindow.appName)) {
155
135
  // Farklı bir pencereye geçildi
156
136
  this.emit("windowLeft", lastWindow);
157
137
  this.emit("windowEntered", currentWindow);
@@ -174,59 +154,6 @@ class WindowSelector extends EventEmitter {
174
154
  return this.selectedWindow;
175
155
  }
176
156
 
177
- /**
178
- * Electron'da kullanmak için mevcut pencereleri döndürür
179
- * @returns {Array} Available windows for selection
180
- */
181
- async getAvailableWindows() {
182
- if (this.isElectron) {
183
- try {
184
- // Start selection to populate window list (safe mode)
185
- const success = nativeBinding.startWindowSelection();
186
- if (success) {
187
- // Get the populated window list
188
- const status = nativeBinding.getWindowSelectionStatus();
189
-
190
- // Stop selection immediately
191
- nativeBinding.stopWindowSelection();
192
-
193
- // Return windows from native status
194
- // In Electron safe mode, these will be available without overlay
195
- const MacRecorder = require("./index.js");
196
- const recorder = new MacRecorder();
197
- return await recorder.getWindows();
198
- }
199
- } catch (error) {
200
- console.error("Error getting available windows in Electron:", error.message);
201
- }
202
- }
203
-
204
- // Fallback to regular MacRecorder method
205
- try {
206
- const MacRecorder = require("./index.js");
207
- const recorder = new MacRecorder();
208
- return await recorder.getWindows();
209
- } catch (error) {
210
- console.error("Error getting windows:", error.message);
211
- return [];
212
- }
213
- }
214
-
215
- /**
216
- * Electron'da pencere seçmek için - overlay olmadan
217
- * @param {Object} windowInfo - Selected window from getAvailableWindows()
218
- */
219
- selectWindowById(windowInfo) {
220
- if (!windowInfo || !windowInfo.id) {
221
- throw new Error("Valid window info required");
222
- }
223
-
224
- this.selectedWindow = windowInfo;
225
- this.emit('windowSelected', windowInfo);
226
-
227
- return windowInfo;
228
- }
229
-
230
157
  /**
231
158
  * Seçim durumunu döndürür
232
159
  */
@@ -237,14 +164,14 @@ class WindowSelector extends EventEmitter {
237
164
  isSelecting: this.isSelecting && nativeStatus.isSelecting,
238
165
  hasSelectedWindow: !!this.selectedWindow,
239
166
  selectedWindow: this.selectedWindow,
240
- nativeStatus: nativeStatus,
167
+ nativeStatus: nativeStatus
241
168
  };
242
169
  } catch (error) {
243
170
  return {
244
171
  isSelecting: this.isSelecting,
245
172
  hasSelectedWindow: !!this.selectedWindow,
246
173
  selectedWindow: this.selectedWindow,
247
- error: error.message,
174
+ error: error.message
248
175
  };
249
176
  }
250
177
  }
@@ -278,6 +205,7 @@ class WindowSelector extends EventEmitter {
278
205
 
279
206
  // Seçimi başlat
280
207
  await this.startSelection();
208
+
281
209
  } catch (error) {
282
210
  this.removeAllListeners("windowSelected");
283
211
  this.removeAllListeners("error");
@@ -314,9 +242,7 @@ class WindowSelector extends EventEmitter {
314
242
  nativeBinding.setBringToFrontEnabled(enabled);
315
243
  // Only log if explicitly setting, not on startup
316
244
  if (arguments.length > 0) {
317
- console.log(
318
- `🔄 Auto bring-to-front: ${enabled ? "ENABLED" : "DISABLED"}`
319
- );
245
+ console.log(`🔄 Auto bring-to-front: ${enabled ? 'ENABLED' : 'DISABLED'}`);
320
246
  }
321
247
  } catch (error) {
322
248
  throw new Error(`Failed to set bring to front: ${error.message}`);
@@ -450,14 +376,14 @@ class WindowSelector extends EventEmitter {
450
376
  try {
451
377
  // Start screen selection
452
378
  await this.startScreenSelection();
453
-
379
+
454
380
  // Poll for selection completion
455
381
  return new Promise((resolve, reject) => {
456
382
  let isResolved = false;
457
-
383
+
458
384
  const checkSelection = () => {
459
385
  if (isResolved) return; // Prevent multiple resolutions
460
-
386
+
461
387
  const selectedScreen = this.getSelectedScreen();
462
388
  if (selectedScreen) {
463
389
  isResolved = true;
@@ -468,19 +394,19 @@ class WindowSelector extends EventEmitter {
468
394
  } else {
469
395
  // Selection was cancelled (probably ESC key)
470
396
  isResolved = true;
471
- reject(new Error("Screen selection was cancelled"));
397
+ reject(new Error('Screen selection was cancelled'));
472
398
  }
473
399
  };
474
-
400
+
475
401
  // Start polling
476
402
  checkSelection();
477
-
403
+
478
404
  // Timeout after 60 seconds
479
405
  setTimeout(() => {
480
406
  if (!isResolved) {
481
407
  isResolved = true;
482
408
  this.stopScreenSelection();
483
- reject(new Error("Screen selection timed out"));
409
+ reject(new Error('Screen selection timed out'));
484
410
  }
485
411
  }, 60000);
486
412
  });
@@ -504,9 +430,7 @@ class WindowSelector extends EventEmitter {
504
430
  const success = nativeBinding.showScreenRecordingPreview(screenInfo);
505
431
  return success;
506
432
  } catch (error) {
507
- throw new Error(
508
- `Failed to show screen recording preview: ${error.message}`
509
- );
433
+ throw new Error(`Failed to show screen recording preview: ${error.message}`);
510
434
  }
511
435
  }
512
436
 
@@ -519,9 +443,7 @@ class WindowSelector extends EventEmitter {
519
443
  const success = nativeBinding.hideScreenRecordingPreview();
520
444
  return success;
521
445
  } catch (error) {
522
- throw new Error(
523
- `Failed to hide screen recording preview: ${error.message}`
524
- );
446
+ throw new Error(`Failed to hide screen recording preview: ${error.message}`);
525
447
  }
526
448
  }
527
449
 
@@ -538,10 +460,10 @@ class WindowSelector extends EventEmitter {
538
460
  return {
539
461
  screenRecording: false,
540
462
  accessibility: false,
541
- error: error.message,
463
+ error: error.message
542
464
  };
543
465
  }
544
466
  }
545
467
  }
546
468
 
547
- module.exports = WindowSelector;
469
+ module.exports = WindowSelector;
@@ -1,342 +0,0 @@
1
- # Electron Integration Guide
2
-
3
- Bu döküman `node-mac-recorder` paketinin Electron uygulamaları ile güvenli entegrasyonunu açıklar.
4
-
5
- ## 🔧 Sorun Çözümü
6
-
7
- **Problem**: ScreenCaptureKit'e geçtikten sonra Electron uygulamalarında native NSWindow overlay'lar crash'e sebep oluyordu.
8
-
9
- **Çözüm**: `ElectronWindowSelector` sınıfı otomatik olarak Electron ortamını tespit eder ve güvenli mod kullanır.
10
-
11
- ## 📋 Electron Güvenli Mod Özellikleri
12
-
13
- ### 🔍 Otomatik Tespit
14
- ```javascript
15
- // Otomatik olarak tespit edilen environment variable'lar:
16
- - process.versions.electron
17
- - process.env.ELECTRON_VERSION
18
- - process.env.ELECTRON_RUN_AS_NODE
19
- ```
20
-
21
- ### 🚫 Native Overlay'lar Devre Dışı
22
- Electron modunda aşağıdaki native işlevler güvenli modda çalışır:
23
- - ✅ Window listing (güvenli)
24
- - ❌ Native NSWindow overlays (devre dışı)
25
- - ❌ Native recording preview overlays (devre dışı)
26
-
27
- ## 🎯 API Kullanımı
28
-
29
- ### Temel Kurulum
30
- ```javascript
31
- const ElectronWindowSelector = require('node-mac-recorder/electron-window-selector');
32
-
33
- const selector = new ElectronWindowSelector();
34
- console.log(`Environment: ${selector.isElectron ? 'Electron' : 'Node.js'}`);
35
- ```
36
-
37
- ### Pencere Seçimi (Electron Safe Mode)
38
- ```javascript
39
- // 1. Mevcut pencere listesini al
40
- const windows = await selector.getAvailableWindows();
41
- console.log(`Found ${windows.length} windows`);
42
-
43
- // 2. Pencere seçim başlat (otomatik mode - demo amaçlı)
44
- const selectedWindow = await selector.selectWindow();
45
-
46
- // 3. Event listener ile dinle
47
- selector.on('windowSelected', (windowInfo) => {
48
- console.log('Selected:', windowInfo.title, windowInfo.appName);
49
-
50
- // Electron UI'da bu window'u highlight et
51
- showWindowInElectronUI(windowInfo);
52
- });
53
- ```
54
-
55
- ### Display Seçimi (Electron Safe Mode)
56
- ```javascript
57
- // 1. Mevcut display listesini al
58
- const displays = await selector.getAvailableDisplays();
59
- console.log(`Found ${displays.length} displays`);
60
-
61
- // 2. Display seçim başlat (otomatik mode - demo amaçlı)
62
- const selectedDisplay = await selector.selectScreen();
63
-
64
- // 3. Event listener ile dinle
65
- selector.on('screenSelected', (screenInfo) => {
66
- console.log('Selected Display:', screenInfo.name, screenInfo.resolution);
67
-
68
- // Electron UI'da bu display'i highlight et
69
- showDisplayInElectronUI(screenInfo);
70
- });
71
- ```
72
-
73
- ## 🎨 Electron UI Implementation Önerisi
74
-
75
- ### 1. Window Picker UI Component
76
-
77
- ```html
78
- <!-- Electron Renderer Process -->
79
- <div class="window-picker">
80
- <h3>Select Window to Record</h3>
81
- <div class="window-grid">
82
- <!-- Her window için thumbnail ve bilgi -->
83
- <div v-for="window in availableWindows"
84
- :key="window.id"
85
- class="window-card"
86
- :class="{ selected: selectedWindow?.id === window.id }"
87
- @click="selectWindow(window)">
88
-
89
- <div class="window-thumbnail">
90
- <!-- Thumbnail görseli buraya -->
91
- <img :src="window.thumbnail" v-if="window.thumbnail" />
92
- <div class="window-placeholder" v-else>
93
- {{ window.appName?.charAt(0) || '?' }}
94
- </div>
95
- </div>
96
-
97
- <div class="window-info">
98
- <div class="app-name">{{ window.appName || 'Unknown App' }}</div>
99
- <div class="window-title">{{ window.title || 'Untitled' }}</div>
100
- <div class="window-size">{{ window.width }}×{{ window.height }}</div>
101
- </div>
102
- </div>
103
- </div>
104
- </div>
105
- ```
106
-
107
- ### 2. Renderer Process Logic
108
- ```javascript
109
- // renderer.js
110
- const { ipcRenderer } = require('electron');
111
-
112
- let windowSelector = null;
113
-
114
- async function initializeWindowPicker() {
115
- // Main process'ten window selector'ı başlat
116
- const result = await ipcRenderer.invoke('init-window-selector');
117
-
118
- if (result.success) {
119
- // Mevcut windows'ları al
120
- const windows = await ipcRenderer.invoke('get-available-windows');
121
- displayWindowsInUI(windows);
122
- }
123
- }
124
-
125
- function displayWindowsInUI(windows) {
126
- const windowGrid = document.querySelector('.window-grid');
127
- windowGrid.innerHTML = '';
128
-
129
- windows.forEach(window => {
130
- const windowCard = createWindowCard(window);
131
- windowGrid.appendChild(windowCard);
132
- });
133
- }
134
-
135
- function createWindowCard(window) {
136
- const card = document.createElement('div');
137
- card.className = 'window-card';
138
- card.innerHTML = `
139
- <div class="window-thumbnail">
140
- <div class="window-placeholder">${window.appName?.charAt(0) || '?'}</div>
141
- </div>
142
- <div class="window-info">
143
- <div class="app-name">${window.appName || 'Unknown App'}</div>
144
- <div class="window-title">${window.title || 'Untitled'}</div>
145
- <div class="window-size">${window.width}×${window.height}</div>
146
- </div>
147
- `;
148
-
149
- card.addEventListener('click', () => selectWindow(window));
150
- return card;
151
- }
152
-
153
- function selectWindow(window) {
154
- // UI'da seçimi görsel olarak göster
155
- document.querySelectorAll('.window-card').forEach(card =>
156
- card.classList.remove('selected')
157
- );
158
- event.target.closest('.window-card').classList.add('selected');
159
-
160
- // Main process'e seçimi bildir
161
- ipcRenderer.invoke('window-selected', window);
162
- }
163
- ```
164
-
165
- ### 3. Main Process Handler
166
- ```javascript
167
- // main.js
168
- const { ipcMain } = require('electron');
169
- const ElectronWindowSelector = require('node-mac-recorder/electron-window-selector');
170
-
171
- let windowSelector = null;
172
-
173
- ipcMain.handle('init-window-selector', async () => {
174
- try {
175
- windowSelector = new ElectronWindowSelector();
176
- return { success: true };
177
- } catch (error) {
178
- return { success: false, error: error.message };
179
- }
180
- });
181
-
182
- ipcMain.handle('get-available-windows', async () => {
183
- if (!windowSelector) return [];
184
- return await windowSelector.getAvailableWindows();
185
- });
186
-
187
- ipcMain.handle('get-available-displays', async () => {
188
- if (!windowSelector) return [];
189
- return await windowSelector.getAvailableDisplays();
190
- });
191
-
192
- ipcMain.handle('window-selected', async (event, windowInfo) => {
193
- console.log('Window selected in Electron UI:', windowInfo);
194
-
195
- // Recording başlatılabilir
196
- const MacRecorder = require('node-mac-recorder');
197
- const recorder = new MacRecorder();
198
-
199
- // Window recording başlat
200
- await recorder.startRecording('./output.mov', {
201
- windowId: windowInfo.id,
202
- // ... diğer options
203
- });
204
-
205
- return { success: true };
206
- });
207
- ```
208
-
209
- ## 🎬 Recording Preview (Electron Mode)
210
-
211
- Electron modunda native preview'lar çalışmaz. Bunun yerine Electron UI'da preview gösterin:
212
-
213
- ```javascript
214
- // Recording preview'ı Electron UI'da göster
215
- function showRecordingPreview(windowInfo) {
216
- // Electron window'da overlay div oluştur
217
- const overlay = document.createElement('div');
218
- overlay.className = 'recording-preview-overlay';
219
- overlay.style.cssText = `
220
- position: fixed;
221
- top: 0;
222
- left: 0;
223
- right: 0;
224
- bottom: 0;
225
- background: rgba(0, 0, 0, 0.5);
226
- z-index: 9999;
227
- display: flex;
228
- align-items: center;
229
- justify-content: center;
230
- `;
231
-
232
- overlay.innerHTML = `
233
- <div class="preview-info">
234
- <h3>Recording Preview</h3>
235
- <p>Recording: ${windowInfo.appName} - ${windowInfo.title}</p>
236
- <p>Area: ${windowInfo.width}×${windowInfo.height}</p>
237
- <button id="start-recording">Start Recording</button>
238
- <button id="cancel-preview">Cancel</button>
239
- </div>
240
- `;
241
-
242
- document.body.appendChild(overlay);
243
- }
244
- ```
245
-
246
- ## 🔧 CSS Styling
247
- ```css
248
- .window-picker {
249
- padding: 20px;
250
- max-height: 500px;
251
- overflow-y: auto;
252
- }
253
-
254
- .window-grid {
255
- display: grid;
256
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
257
- gap: 15px;
258
- margin-top: 15px;
259
- }
260
-
261
- .window-card {
262
- border: 2px solid #e1e1e1;
263
- border-radius: 8px;
264
- padding: 10px;
265
- cursor: pointer;
266
- transition: all 0.2s;
267
- }
268
-
269
- .window-card:hover {
270
- border-color: #007acc;
271
- background-color: #f0f8ff;
272
- }
273
-
274
- .window-card.selected {
275
- border-color: #007acc;
276
- background-color: #e6f3ff;
277
- }
278
-
279
- .window-thumbnail {
280
- height: 80px;
281
- background: #f5f5f5;
282
- border-radius: 4px;
283
- display: flex;
284
- align-items: center;
285
- justify-content: center;
286
- margin-bottom: 8px;
287
- }
288
-
289
- .window-placeholder {
290
- font-size: 24px;
291
- font-weight: bold;
292
- color: #666;
293
- }
294
-
295
- .window-info {
296
- text-align: center;
297
- }
298
-
299
- .app-name {
300
- font-weight: bold;
301
- color: #333;
302
- margin-bottom: 2px;
303
- }
304
-
305
- .window-title {
306
- color: #666;
307
- font-size: 12px;
308
- margin-bottom: 2px;
309
- }
310
-
311
- .window-size {
312
- color: #999;
313
- font-size: 11px;
314
- }
315
- ```
316
-
317
- ## ⚠️ Önemli Notlar
318
-
319
- 1. **Native Overlays**: Electron modunda native NSWindow overlays devre dışıdır
320
- 2. **Auto Selection**: Şu an demo amaçlı otomatik seçim yapıyor, gerçek uygulamada UI ile seçim yapılmalı
321
- 3. **Permission Check**: `checkPermissions()` tüm modlarda çalışır
322
- 4. **Event Handling**: Electron'da event'ler IPC ile main ve renderer process arasında taşınmalı
323
-
324
- ## 🚀 Sonraki Adımlar
325
-
326
- 1. Thumbnail generation implementasyonu
327
- 2. Real-time window list updates
328
- 3. Multiple display support UI
329
- 4. Recording progress indicator
330
- 5. Custom recording area selection
331
-
332
- ## 📞 Test Komutları
333
-
334
- ```bash
335
- # Electron mode test
336
- ELECTRON_VERSION=25.0.0 node test-electron-window-selector.js
337
-
338
- # Node.js mode test
339
- node test-electron-window-selector.js
340
- ```
341
-
342
- Bu implementasyon sayesinde Electron uygulamaları crash olmadan pencere seçimi yapabilir ve recording işlevlerini güvenli şekilde kullanabilir.