node-mac-recorder 2.4.6 → 2.4.8
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/.claude/settings.local.json +4 -1
- package/WINDOW_SELECTOR_USAGE.md +447 -0
- package/package.json +1 -1
- package/src/window_selector.mm +77 -14
- package/window-selector.js +54 -19
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"WebFetch(domain:github.com)",
|
|
23
23
|
"WebFetch(domain:nonstrict.eu)",
|
|
24
24
|
"Bash(cp:*)",
|
|
25
|
-
"Bash(git checkout:*)"
|
|
25
|
+
"Bash(git checkout:*)",
|
|
26
|
+
"Bash(ELECTRON_VERSION=25.0.0 node -e \"\nconsole.log(''ELECTRON_VERSION env:'', process.env.ELECTRON_VERSION);\nconsole.log(''getenv result would be:'', process.env.ELECTRON_VERSION || ''null'');\n\")",
|
|
27
|
+
"Bash(ELECTRON_VERSION=25.0.0 node test-env-detection.js)",
|
|
28
|
+
"Bash(ELECTRON_VERSION=25.0.0 node test-native-call.js)"
|
|
26
29
|
],
|
|
27
30
|
"deny": []
|
|
28
31
|
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Window Selector Usage Guide
|
|
2
|
+
|
|
3
|
+
The `WindowSelector` module provides native macOS window and screen selection with overlay interfaces. This guide shows how to use it in both Node.js and Electron applications.
|
|
4
|
+
|
|
5
|
+
## Basic Setup
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const MacRecorder = require('node-mac-recorder');
|
|
9
|
+
const WindowSelector = MacRecorder.WindowSelector;
|
|
10
|
+
|
|
11
|
+
const selector = new WindowSelector();
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Core Features
|
|
15
|
+
|
|
16
|
+
### 1. Window Selection with Overlay
|
|
17
|
+
|
|
18
|
+
Select any window on screen with visual highlights:
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// Method 1: Promise-based selection
|
|
22
|
+
async function selectWindow() {
|
|
23
|
+
try {
|
|
24
|
+
const selectedWindow = await selector.selectWindow();
|
|
25
|
+
console.log('Selected window:', selectedWindow);
|
|
26
|
+
// Returns: { id, title, appName, x, y, width, height }
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log('Selection cancelled or failed:', error.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Method 2: Event-based selection with more control
|
|
33
|
+
async function selectWindowWithEvents() {
|
|
34
|
+
// Listen for events
|
|
35
|
+
selector.on('windowEntered', (window) => {
|
|
36
|
+
console.log('Mouse over window:', window.title);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
selector.on('windowLeft', (window) => {
|
|
40
|
+
console.log('Mouse left window:', window.title);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
selector.on('windowSelected', (window) => {
|
|
44
|
+
console.log('Window selected:', window);
|
|
45
|
+
// Handle selection
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Start selection
|
|
49
|
+
await selector.startSelection();
|
|
50
|
+
|
|
51
|
+
// Selection runs until user clicks or you call stopSelection()
|
|
52
|
+
// setTimeout(() => selector.stopSelection(), 10000); // Auto-stop after 10s
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Screen Selection with Overlay
|
|
57
|
+
|
|
58
|
+
Select entire screens (useful for multi-monitor setups):
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
async function selectScreen() {
|
|
62
|
+
try {
|
|
63
|
+
const selectedScreen = await selector.selectScreen();
|
|
64
|
+
console.log('Selected screen:', selectedScreen);
|
|
65
|
+
// Returns: { id, width, height, x, y, name }
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log('Screen selection cancelled:', error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Manual control
|
|
72
|
+
async function manualScreenSelection() {
|
|
73
|
+
await selector.startScreenSelection();
|
|
74
|
+
|
|
75
|
+
// Check for selection periodically
|
|
76
|
+
const checkSelection = setInterval(() => {
|
|
77
|
+
const selected = selector.getSelectedScreen();
|
|
78
|
+
if (selected) {
|
|
79
|
+
console.log('Screen selected:', selected);
|
|
80
|
+
clearInterval(checkSelection);
|
|
81
|
+
selector.stopScreenSelection();
|
|
82
|
+
}
|
|
83
|
+
}, 100);
|
|
84
|
+
|
|
85
|
+
// Auto-cancel after 30 seconds
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
clearInterval(checkSelection);
|
|
88
|
+
selector.stopScreenSelection();
|
|
89
|
+
}, 30000);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Recording Preview Overlays
|
|
94
|
+
|
|
95
|
+
Show preview of what will be recorded:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
async function showRecordingPreview() {
|
|
99
|
+
// Get a window first
|
|
100
|
+
const recorder = new MacRecorder();
|
|
101
|
+
const windows = await recorder.getWindows();
|
|
102
|
+
const targetWindow = windows[0];
|
|
103
|
+
|
|
104
|
+
// Show preview overlay (darkens screen, highlights window)
|
|
105
|
+
await selector.showRecordingPreview(targetWindow);
|
|
106
|
+
|
|
107
|
+
// Show for 3 seconds
|
|
108
|
+
setTimeout(async () => {
|
|
109
|
+
await selector.hideRecordingPreview();
|
|
110
|
+
}, 3000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Screen recording preview
|
|
114
|
+
async function showScreenRecordingPreview() {
|
|
115
|
+
const recorder = new MacRecorder();
|
|
116
|
+
const displays = await recorder.getDisplays();
|
|
117
|
+
const targetScreen = displays[0];
|
|
118
|
+
|
|
119
|
+
await selector.showScreenRecordingPreview(targetScreen);
|
|
120
|
+
|
|
121
|
+
// Hide after delay
|
|
122
|
+
setTimeout(async () => {
|
|
123
|
+
await selector.hideScreenRecordingPreview();
|
|
124
|
+
}, 3000);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4. Status and Cleanup
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// Check current status
|
|
132
|
+
const status = selector.getStatus();
|
|
133
|
+
console.log(status);
|
|
134
|
+
// Returns: { isSelecting, hasSelectedWindow, selectedWindow, nativeStatus }
|
|
135
|
+
|
|
136
|
+
// Cleanup when done
|
|
137
|
+
await selector.cleanup();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Electron Integration
|
|
141
|
+
|
|
142
|
+
**IMPORTANT**: In Electron environments, the window selector automatically switches to "safe mode" to prevent NSWindow overlay crashes. Instead of creating native overlays, it provides window/screen lists that you can display in your Electron UI.
|
|
143
|
+
|
|
144
|
+
### Main Process Usage
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// In main.cjs or main.js
|
|
148
|
+
const { ipcMain } = require('electron');
|
|
149
|
+
const MacRecorder = require('node-mac-recorder');
|
|
150
|
+
const WindowSelector = MacRecorder.WindowSelector;
|
|
151
|
+
|
|
152
|
+
let windowSelector = null;
|
|
153
|
+
|
|
154
|
+
ipcMain.handle('window-selector-init', () => {
|
|
155
|
+
windowSelector = new WindowSelector();
|
|
156
|
+
// In Electron, this will automatically use safe mode
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Get available windows (safe for Electron)
|
|
161
|
+
ipcMain.handle('get-available-windows', async () => {
|
|
162
|
+
try {
|
|
163
|
+
const windows = await windowSelector.getAvailableWindows();
|
|
164
|
+
return { success: true, windows: windows };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return { success: false, error: error.message };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Select window by ID (no overlay needed)
|
|
171
|
+
ipcMain.handle('select-window-by-id', async (event, windowInfo) => {
|
|
172
|
+
try {
|
|
173
|
+
const selectedWindow = windowSelector.selectWindowById(windowInfo);
|
|
174
|
+
return { success: true, window: selectedWindow };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return { success: false, error: error.message };
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Get available screens (safe for Electron)
|
|
181
|
+
ipcMain.handle('get-available-screens', async () => {
|
|
182
|
+
try {
|
|
183
|
+
const recorder = new MacRecorder();
|
|
184
|
+
const screens = await recorder.getDisplays();
|
|
185
|
+
return { success: true, screens: screens };
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return { success: false, error: error.message };
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
ipcMain.handle('window-selector-cleanup', async () => {
|
|
192
|
+
if (windowSelector) {
|
|
193
|
+
await windowSelector.cleanup();
|
|
194
|
+
windowSelector = null;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Renderer Process Usage
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
// In renderer.js or React component
|
|
204
|
+
const { ipcRenderer } = require('electron');
|
|
205
|
+
|
|
206
|
+
class ScreenRecorder {
|
|
207
|
+
async selectWindow() {
|
|
208
|
+
// Initialize selector
|
|
209
|
+
await ipcRenderer.invoke('window-selector-init');
|
|
210
|
+
|
|
211
|
+
// Select window
|
|
212
|
+
const result = await ipcRenderer.invoke('window-selector-select');
|
|
213
|
+
|
|
214
|
+
if (result.success) {
|
|
215
|
+
console.log('Selected window:', result.window);
|
|
216
|
+
return result.window;
|
|
217
|
+
} else {
|
|
218
|
+
throw new Error(result.error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async selectScreen() {
|
|
223
|
+
await ipcRenderer.invoke('window-selector-init');
|
|
224
|
+
|
|
225
|
+
const result = await ipcRenderer.invoke('screen-selector-select');
|
|
226
|
+
|
|
227
|
+
if (result.success) {
|
|
228
|
+
console.log('Selected screen:', result.screen);
|
|
229
|
+
return result.screen;
|
|
230
|
+
} else {
|
|
231
|
+
throw new Error(result.error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async cleanup() {
|
|
236
|
+
await ipcRenderer.invoke('window-selector-cleanup');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Usage in React component
|
|
241
|
+
function RecordingComponent() {
|
|
242
|
+
const [selectedWindow, setSelectedWindow] = useState(null);
|
|
243
|
+
const recorder = new ScreenRecorder();
|
|
244
|
+
|
|
245
|
+
const handleSelectWindow = async () => {
|
|
246
|
+
try {
|
|
247
|
+
const window = await recorder.selectWindow();
|
|
248
|
+
setSelectedWindow(window);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error('Window selection failed:', error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div>
|
|
256
|
+
<button onClick={handleSelectWindow}>
|
|
257
|
+
Select Window to Record
|
|
258
|
+
</button>
|
|
259
|
+
{selectedWindow && (
|
|
260
|
+
<div>
|
|
261
|
+
Selected: {selectedWindow.title} ({selectedWindow.appName})
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Complete Electron Example
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// main.cjs
|
|
273
|
+
const { app, BrowserWindow, ipcMain } = require('electron');
|
|
274
|
+
const MacRecorder = require('node-mac-recorder');
|
|
275
|
+
const WindowSelector = MacRecorder.WindowSelector;
|
|
276
|
+
|
|
277
|
+
let mainWindow;
|
|
278
|
+
let windowSelector;
|
|
279
|
+
let recorder;
|
|
280
|
+
|
|
281
|
+
function createWindow() {
|
|
282
|
+
mainWindow = new BrowserWindow({
|
|
283
|
+
width: 800,
|
|
284
|
+
height: 600,
|
|
285
|
+
webPreferences: {
|
|
286
|
+
nodeIntegration: true,
|
|
287
|
+
contextIsolation: false
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
mainWindow.loadFile('index.html');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Initialize services
|
|
295
|
+
ipcMain.handle('init-services', async () => {
|
|
296
|
+
recorder = new MacRecorder();
|
|
297
|
+
windowSelector = new WindowSelector();
|
|
298
|
+
return true;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Window selection
|
|
302
|
+
ipcMain.handle('select-window', async () => {
|
|
303
|
+
try {
|
|
304
|
+
const window = await windowSelector.selectWindow();
|
|
305
|
+
return { success: true, data: window };
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return { success: false, error: error.message };
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Screen selection
|
|
312
|
+
ipcMain.handle('select-screen', async () => {
|
|
313
|
+
try {
|
|
314
|
+
const screen = await windowSelector.selectScreen();
|
|
315
|
+
return { success: true, data: screen };
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return { success: false, error: error.message };
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Start recording
|
|
322
|
+
ipcMain.handle('start-recording', async (event, windowInfo, outputPath) => {
|
|
323
|
+
try {
|
|
324
|
+
const options = {
|
|
325
|
+
windowId: windowInfo.id,
|
|
326
|
+
captureCursor: true,
|
|
327
|
+
includeSystemAudio: false
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
await recorder.startRecording(outputPath, options);
|
|
331
|
+
return { success: true };
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return { success: false, error: error.message };
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Stop recording
|
|
338
|
+
ipcMain.handle('stop-recording', async () => {
|
|
339
|
+
try {
|
|
340
|
+
await recorder.stopRecording();
|
|
341
|
+
return { success: true };
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return { success: false, error: error.message };
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
app.whenReady().then(createWindow);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```html
|
|
351
|
+
<!-- index.html -->
|
|
352
|
+
<!DOCTYPE html>
|
|
353
|
+
<html>
|
|
354
|
+
<head>
|
|
355
|
+
<title>Screen Recorder</title>
|
|
356
|
+
</head>
|
|
357
|
+
<body>
|
|
358
|
+
<h1>Screen Recorder</h1>
|
|
359
|
+
|
|
360
|
+
<button id="selectWindow">Select Window</button>
|
|
361
|
+
<button id="selectScreen">Select Screen</button>
|
|
362
|
+
<button id="startRecord">Start Recording</button>
|
|
363
|
+
<button id="stopRecord">Stop Recording</button>
|
|
364
|
+
|
|
365
|
+
<div id="status"></div>
|
|
366
|
+
|
|
367
|
+
<script>
|
|
368
|
+
const { ipcRenderer } = require('electron');
|
|
369
|
+
|
|
370
|
+
let selectedWindow = null;
|
|
371
|
+
let selectedScreen = null;
|
|
372
|
+
|
|
373
|
+
// Initialize
|
|
374
|
+
ipcRenderer.invoke('init-services');
|
|
375
|
+
|
|
376
|
+
document.getElementById('selectWindow').addEventListener('click', async () => {
|
|
377
|
+
const result = await ipcRenderer.invoke('select-window');
|
|
378
|
+
if (result.success) {
|
|
379
|
+
selectedWindow = result.data;
|
|
380
|
+
document.getElementById('status').innerHTML =
|
|
381
|
+
`Selected Window: ${selectedWindow.title}`;
|
|
382
|
+
} else {
|
|
383
|
+
alert('Window selection failed: ' + result.error);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
document.getElementById('selectScreen').addEventListener('click', async () => {
|
|
388
|
+
const result = await ipcRenderer.invoke('select-screen');
|
|
389
|
+
if (result.success) {
|
|
390
|
+
selectedScreen = result.data;
|
|
391
|
+
document.getElementById('status').innerHTML =
|
|
392
|
+
`Selected Screen: ${selectedScreen.width}x${selectedScreen.height}`;
|
|
393
|
+
} else {
|
|
394
|
+
alert('Screen selection failed: ' + result.error);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
document.getElementById('startRecord').addEventListener('click', async () => {
|
|
399
|
+
if (!selectedWindow) {
|
|
400
|
+
alert('Please select a window first');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const outputPath = `/tmp/recording-${Date.now()}.mov`;
|
|
405
|
+
const result = await ipcRenderer.invoke('start-recording', selectedWindow, outputPath);
|
|
406
|
+
|
|
407
|
+
if (result.success) {
|
|
408
|
+
document.getElementById('status').innerHTML = 'Recording started...';
|
|
409
|
+
} else {
|
|
410
|
+
alert('Recording failed: ' + result.error);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
document.getElementById('stopRecord').addEventListener('click', async () => {
|
|
415
|
+
const result = await ipcRenderer.invoke('stop-recording');
|
|
416
|
+
if (result.success) {
|
|
417
|
+
document.getElementById('status').innerHTML = 'Recording stopped';
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
</script>
|
|
421
|
+
</body>
|
|
422
|
+
</html>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Key Points for AI Integration
|
|
426
|
+
|
|
427
|
+
1. **Asynchronous Operations**: All selection methods return Promises
|
|
428
|
+
2. **Event-Driven**: Use events for real-time feedback during selection
|
|
429
|
+
3. **Error Handling**: Always wrap in try-catch blocks
|
|
430
|
+
4. **Cleanup Required**: Call `cleanup()` when done to prevent memory leaks
|
|
431
|
+
5. **Permissions Required**: Needs macOS screen recording permissions
|
|
432
|
+
6. **Multi-Monitor Support**: Screen selection works with multiple displays
|
|
433
|
+
7. **Window Filtering**: Automatically filters out invalid/hidden windows
|
|
434
|
+
|
|
435
|
+
## Permissions
|
|
436
|
+
|
|
437
|
+
The module requires macOS screen recording permissions. Users will be prompted automatically, or check programmatically:
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
const permissions = await selector.checkPermissions();
|
|
441
|
+
if (!permissions.screenRecording) {
|
|
442
|
+
console.log('Screen recording permission required');
|
|
443
|
+
// Guide user to System Preferences > Privacy & Security > Screen Recording
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
This covers all major use cases for integrating window/screen selection into AI-powered applications.
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -974,10 +974,40 @@ bool hideScreenRecordingPreview() {
|
|
|
974
974
|
Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
975
975
|
Napi::Env env = info.Env();
|
|
976
976
|
|
|
977
|
-
// Electron safety check - prevent crashes
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
977
|
+
// Electron safety check - prevent NSWindow crashes
|
|
978
|
+
const char* electronVersion = getenv("ELECTRON_VERSION");
|
|
979
|
+
const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
|
|
980
|
+
|
|
981
|
+
NSLog(@"🔍 Debug: electronVersion='%s', electronRunAs='%s'",
|
|
982
|
+
electronVersion ? electronVersion : "null",
|
|
983
|
+
electronRunAs ? electronRunAs : "null");
|
|
984
|
+
|
|
985
|
+
if (electronVersion || electronRunAs) {
|
|
986
|
+
NSLog(@"🔍 Detected Electron environment - using safe mode");
|
|
987
|
+
|
|
988
|
+
// In Electron, return window list without creating native NSWindow overlays
|
|
989
|
+
// The Electron app can handle UI selection itself
|
|
990
|
+
@try {
|
|
991
|
+
NSArray *windows = getAllSelectableWindows();
|
|
992
|
+
|
|
993
|
+
if (!windows || [windows count] == 0) {
|
|
994
|
+
NSLog(@"❌ No selectable windows found");
|
|
995
|
+
return Napi::Boolean::New(env, false);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Store windows for later retrieval via getWindowSelectionStatus
|
|
999
|
+
g_allWindows = [windows mutableCopy];
|
|
1000
|
+
g_isWindowSelecting = true;
|
|
1001
|
+
|
|
1002
|
+
// Return true to indicate windows are available
|
|
1003
|
+
// Electron app should call getWindowSelectionStatus to get the list
|
|
1004
|
+
NSLog(@"✅ Electron-safe mode: %lu windows available for selection", (unsigned long)[windows count]);
|
|
1005
|
+
return Napi::Boolean::New(env, true);
|
|
1006
|
+
|
|
1007
|
+
} @catch (NSException *exception) {
|
|
1008
|
+
NSLog(@"❌ Exception in Electron-safe window selection: %@", [exception reason]);
|
|
1009
|
+
return Napi::Boolean::New(env, false);
|
|
1010
|
+
}
|
|
981
1011
|
}
|
|
982
1012
|
|
|
983
1013
|
if (g_isWindowSelecting) {
|
|
@@ -1279,12 +1309,6 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
|
|
|
1279
1309
|
Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
|
|
1280
1310
|
Napi::Env env = info.Env();
|
|
1281
1311
|
|
|
1282
|
-
// Electron safety check - prevent crashes with overlay windows
|
|
1283
|
-
if (getenv("ELECTRON_VERSION") || getenv("ELECTRON_RUN_AS_NODE")) {
|
|
1284
|
-
NSLog(@"⚠️ Recording preview disabled in Electron environment to prevent crashes");
|
|
1285
|
-
return Napi::Boolean::New(env, false);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
1312
|
if (info.Length() < 1) {
|
|
1289
1313
|
NSLog(@"⚠️ Window info object required");
|
|
1290
1314
|
return Napi::Boolean::New(env, false);
|
|
@@ -1350,10 +1374,49 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
|
|
|
1350
1374
|
Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
|
|
1351
1375
|
Napi::Env env = info.Env();
|
|
1352
1376
|
|
|
1353
|
-
// Electron safety check - prevent crashes
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1377
|
+
// Electron safety check - prevent NSWindow crashes
|
|
1378
|
+
const char* electronVersion = getenv("ELECTRON_VERSION");
|
|
1379
|
+
const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
|
|
1380
|
+
|
|
1381
|
+
NSLog(@"🔍 Screen Debug: electronVersion='%s', electronRunAs='%s'",
|
|
1382
|
+
electronVersion ? electronVersion : "null",
|
|
1383
|
+
electronRunAs ? electronRunAs : "null");
|
|
1384
|
+
|
|
1385
|
+
if (electronVersion || electronRunAs) {
|
|
1386
|
+
NSLog(@"🔍 Detected Electron environment - using safe screen selection");
|
|
1387
|
+
|
|
1388
|
+
// In Electron, return screen list without creating native NSWindow overlays
|
|
1389
|
+
@try {
|
|
1390
|
+
NSArray *screens = [NSScreen screens];
|
|
1391
|
+
|
|
1392
|
+
if (!screens || [screens count] == 0) {
|
|
1393
|
+
NSLog(@"❌ No screens available");
|
|
1394
|
+
return Napi::Boolean::New(env, false);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Store screens and select first one automatically for Electron
|
|
1398
|
+
g_allScreens = screens;
|
|
1399
|
+
g_isScreenSelecting = true;
|
|
1400
|
+
|
|
1401
|
+
NSScreen *mainScreen = [screens firstObject];
|
|
1402
|
+
g_selectedScreenInfo = @{
|
|
1403
|
+
@"id": @((int)[screens indexOfObject:mainScreen]),
|
|
1404
|
+
@"width": @((int)mainScreen.frame.size.width),
|
|
1405
|
+
@"height": @((int)mainScreen.frame.size.height),
|
|
1406
|
+
@"x": @((int)mainScreen.frame.origin.x),
|
|
1407
|
+
@"y": @((int)mainScreen.frame.origin.y)
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
// Mark as complete so getSelectedScreenInfo returns the selection
|
|
1411
|
+
g_isScreenSelecting = false;
|
|
1412
|
+
|
|
1413
|
+
NSLog(@"✅ Electron-safe screen selection: %lu screens available", (unsigned long)[screens count]);
|
|
1414
|
+
return Napi::Boolean::New(env, true);
|
|
1415
|
+
|
|
1416
|
+
} @catch (NSException *exception) {
|
|
1417
|
+
NSLog(@"❌ Exception in Electron-safe screen selection: %@", [exception reason]);
|
|
1418
|
+
return Napi::Boolean::New(env, false);
|
|
1419
|
+
}
|
|
1357
1420
|
}
|
|
1358
1421
|
|
|
1359
1422
|
@try {
|
package/window-selector.js
CHANGED
|
@@ -39,7 +39,7 @@ class WindowSelector extends EventEmitter {
|
|
|
39
39
|
!!(process.env.ELECTRON_RUN_AS_NODE);
|
|
40
40
|
|
|
41
41
|
if (this.isElectron) {
|
|
42
|
-
console.
|
|
42
|
+
console.log("🔍 WindowSelector: Detected Electron environment - using safe mode");
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -49,10 +49,6 @@ class WindowSelector extends EventEmitter {
|
|
|
49
49
|
* Select butonuna basılınca seçim tamamlanır
|
|
50
50
|
*/
|
|
51
51
|
async startSelection() {
|
|
52
|
-
if (this.isElectron) {
|
|
53
|
-
throw new Error("Window selection is not supported in Electron environment due to stability issues");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
52
|
if (this.isSelecting) {
|
|
57
53
|
throw new Error("Window selection is already in progress");
|
|
58
54
|
}
|
|
@@ -178,6 +174,59 @@ class WindowSelector extends EventEmitter {
|
|
|
178
174
|
return this.selectedWindow;
|
|
179
175
|
}
|
|
180
176
|
|
|
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
|
+
|
|
181
230
|
/**
|
|
182
231
|
* Seçim durumunu döndürür
|
|
183
232
|
*/
|
|
@@ -304,11 +353,6 @@ class WindowSelector extends EventEmitter {
|
|
|
304
353
|
* @returns {Promise<boolean>} Success/failure
|
|
305
354
|
*/
|
|
306
355
|
async showRecordingPreview(windowInfo) {
|
|
307
|
-
if (this.isElectron) {
|
|
308
|
-
console.warn("⚠️ Recording preview disabled in Electron environment");
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
356
|
if (!windowInfo) {
|
|
313
357
|
throw new Error("Window info is required");
|
|
314
358
|
}
|
|
@@ -340,10 +384,6 @@ class WindowSelector extends EventEmitter {
|
|
|
340
384
|
* @returns {Promise<boolean>} Success/failure
|
|
341
385
|
*/
|
|
342
386
|
async startScreenSelection() {
|
|
343
|
-
if (this.isElectron) {
|
|
344
|
-
throw new Error("Screen selection is not supported in Electron environment due to stability issues");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
387
|
try {
|
|
348
388
|
const success = nativeBinding.startScreenSelection();
|
|
349
389
|
if (success) {
|
|
@@ -456,11 +496,6 @@ class WindowSelector extends EventEmitter {
|
|
|
456
496
|
* @returns {Promise<boolean>} Success/failure
|
|
457
497
|
*/
|
|
458
498
|
async showScreenRecordingPreview(screenInfo) {
|
|
459
|
-
if (this.isElectron) {
|
|
460
|
-
console.warn("⚠️ Screen recording preview disabled in Electron environment");
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
499
|
if (!screenInfo) {
|
|
465
500
|
throw new Error("Screen info is required");
|
|
466
501
|
}
|