node-mac-recorder 2.17.19 → 2.17.21
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 +3 -1
- package/index.js +21 -200
- package/package.json +1 -1
- package/publish.sh +18 -0
- package/src/cursor_tracker.mm +37 -20
- package/cursor-data-1751364226346.json +0 -1
- package/cursor-data-1751364314136.json +0 -1
- package/cursor-data.json +0 -1
- package/cursor-debug-test.js +0 -60
- package/cursor-dpr-test.js +0 -73
- package/cursor-dpr-test.json +0 -1
- package/cursor-macbook-test.js +0 -63
- package/cursor-permission-test.js +0 -46
- package/cursor-scaling-debug.js +0 -53
- package/cursor-simple-test.js +0 -26
- package/debug-audio.js +0 -79
- package/debug-coordinates.js +0 -69
- package/debug-cursor-output.json +0 -1
- package/debug-macos14.js +0 -110
- package/debug-primary-window.js +0 -84
- package/debug-screen-selection.js +0 -81
- package/debug-window-selector.js +0 -178
- package/examples/electron-integration-example.js +0 -230
- package/examples/electron-preload.js +0 -46
- package/examples/electron-renderer.html +0 -634
- package/examples/integration-example.js +0 -228
- package/examples/window-selector-example.js +0 -254
- package/quick-cursor-test.json +0 -1
- package/test-both-cursor.json +0 -1
- package/test-cursor-fix.js +0 -105
- package/test-cursor-types.js +0 -117
- package/test-display-coordinates.js +0 -72
- package/test-integrated-recording.js +0 -120
- package/test-menubar-offset.js +0 -110
- package/test-output/primary-fix-test-1758266910543.json +0 -1
- package/test-output/unified-cursor-1758313640878.json +0 -1
- package/test-output/unified-cursor-1758313689471.json +0 -1
- package/test-primary-cursor.js +0 -154
- package/test-primary-fix.js +0 -120
- package/test-unified-cursor.js +0 -75
- package/test-window-recording.js +0 -149
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Electron-Safe Mac Recorder</title>
|
|
7
|
-
<style>
|
|
8
|
-
body {
|
|
9
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
|
10
|
-
sans-serif;
|
|
11
|
-
margin: 0;
|
|
12
|
-
padding: 20px;
|
|
13
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
14
|
-
color: white;
|
|
15
|
-
min-height: 100vh;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.container {
|
|
19
|
-
max-width: 1000px;
|
|
20
|
-
margin: 0 auto;
|
|
21
|
-
background: rgba(255, 255, 255, 0.1);
|
|
22
|
-
backdrop-filter: blur(10px);
|
|
23
|
-
border-radius: 20px;
|
|
24
|
-
padding: 30px;
|
|
25
|
-
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
h1 {
|
|
29
|
-
text-align: center;
|
|
30
|
-
margin-bottom: 30px;
|
|
31
|
-
font-size: 2.5em;
|
|
32
|
-
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.status-bar {
|
|
36
|
-
background: rgba(255, 255, 255, 0.2);
|
|
37
|
-
padding: 15px;
|
|
38
|
-
border-radius: 10px;
|
|
39
|
-
margin-bottom: 20px;
|
|
40
|
-
display: flex;
|
|
41
|
-
justify-content: space-between;
|
|
42
|
-
align-items: center;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.recording-controls {
|
|
46
|
-
display: flex;
|
|
47
|
-
gap: 15px;
|
|
48
|
-
margin-bottom: 30px;
|
|
49
|
-
flex-wrap: wrap;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.btn {
|
|
53
|
-
padding: 12px 24px;
|
|
54
|
-
border: none;
|
|
55
|
-
border-radius: 8px;
|
|
56
|
-
cursor: pointer;
|
|
57
|
-
font-size: 16px;
|
|
58
|
-
font-weight: 600;
|
|
59
|
-
transition: all 0.3s ease;
|
|
60
|
-
backdrop-filter: blur(10px);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.btn-primary {
|
|
64
|
-
background: rgba(76, 175, 80, 0.8);
|
|
65
|
-
color: white;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.btn-danger {
|
|
69
|
-
background: rgba(244, 67, 54, 0.8);
|
|
70
|
-
color: white;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.btn-secondary {
|
|
74
|
-
background: rgba(255, 255, 255, 0.2);
|
|
75
|
-
color: white;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.btn:hover {
|
|
79
|
-
transform: translateY(-2px);
|
|
80
|
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.btn:disabled {
|
|
84
|
-
opacity: 0.5;
|
|
85
|
-
cursor: not-allowed;
|
|
86
|
-
transform: none;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.options-panel {
|
|
90
|
-
background: rgba(255, 255, 255, 0.1);
|
|
91
|
-
padding: 20px;
|
|
92
|
-
border-radius: 10px;
|
|
93
|
-
margin-bottom: 20px;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.form-group {
|
|
97
|
-
margin-bottom: 15px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
label {
|
|
101
|
-
display: block;
|
|
102
|
-
margin-bottom: 5px;
|
|
103
|
-
font-weight: 600;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
input[type="checkbox"] {
|
|
107
|
-
margin-right: 8px;
|
|
108
|
-
transform: scale(1.2);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
select {
|
|
112
|
-
width: 100%;
|
|
113
|
-
padding: 8px;
|
|
114
|
-
border-radius: 5px;
|
|
115
|
-
border: none;
|
|
116
|
-
background: rgba(255, 255, 255, 0.9);
|
|
117
|
-
color: #333;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.displays-grid,
|
|
121
|
-
.windows-grid {
|
|
122
|
-
display: grid;
|
|
123
|
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
124
|
-
gap: 15px;
|
|
125
|
-
margin-top: 15px;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.display-item,
|
|
129
|
-
.window-item {
|
|
130
|
-
background: rgba(255, 255, 255, 0.1);
|
|
131
|
-
padding: 15px;
|
|
132
|
-
border-radius: 10px;
|
|
133
|
-
cursor: pointer;
|
|
134
|
-
transition: all 0.3s ease;
|
|
135
|
-
border: 2px solid transparent;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.display-item:hover,
|
|
139
|
-
.window-item:hover {
|
|
140
|
-
background: rgba(255, 255, 255, 0.2);
|
|
141
|
-
transform: translateY(-2px);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.display-item.selected,
|
|
145
|
-
.window-item.selected {
|
|
146
|
-
border-color: #4caf50;
|
|
147
|
-
background: rgba(76, 175, 80, 0.2);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.thumbnail {
|
|
151
|
-
width: 100%;
|
|
152
|
-
height: 120px;
|
|
153
|
-
object-fit: cover;
|
|
154
|
-
border-radius: 5px;
|
|
155
|
-
margin-bottom: 10px;
|
|
156
|
-
background: rgba(255, 255, 255, 0.1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.info-section {
|
|
160
|
-
background: rgba(255, 255, 255, 0.1);
|
|
161
|
-
padding: 20px;
|
|
162
|
-
border-radius: 10px;
|
|
163
|
-
margin-bottom: 20px;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.permission-item {
|
|
167
|
-
display: flex;
|
|
168
|
-
justify-content: space-between;
|
|
169
|
-
margin-bottom: 10px;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.status-indicator {
|
|
173
|
-
padding: 4px 12px;
|
|
174
|
-
border-radius: 15px;
|
|
175
|
-
font-size: 12px;
|
|
176
|
-
font-weight: 600;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.status-granted {
|
|
180
|
-
background: rgba(76, 175, 80, 0.8);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.status-denied {
|
|
184
|
-
background: rgba(244, 67, 54, 0.8);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.log-area {
|
|
188
|
-
background: rgba(0, 0, 0, 0.3);
|
|
189
|
-
padding: 15px;
|
|
190
|
-
border-radius: 10px;
|
|
191
|
-
height: 200px;
|
|
192
|
-
overflow-y: auto;
|
|
193
|
-
font-family: "Monaco", "Consolas", monospace;
|
|
194
|
-
font-size: 12px;
|
|
195
|
-
line-height: 1.4;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.recording-time {
|
|
199
|
-
font-size: 1.5em;
|
|
200
|
-
font-weight: bold;
|
|
201
|
-
color: #ff4444;
|
|
202
|
-
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.sections {
|
|
206
|
-
display: grid;
|
|
207
|
-
grid-template-columns: 1fr 1fr;
|
|
208
|
-
gap: 20px;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
@media (max-width: 768px) {
|
|
212
|
-
.sections {
|
|
213
|
-
grid-template-columns: 1fr;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.recording-controls {
|
|
217
|
-
justify-content: center;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
</style>
|
|
221
|
-
</head>
|
|
222
|
-
<body>
|
|
223
|
-
<div class="container">
|
|
224
|
-
<h1>🎬 Electron-Safe Mac Recorder</h1>
|
|
225
|
-
|
|
226
|
-
<div class="status-bar">
|
|
227
|
-
<div>
|
|
228
|
-
<strong>Status:</strong> <span id="recordingStatus">Idle</span>
|
|
229
|
-
</div>
|
|
230
|
-
<div class="recording-time" id="recordingTime">00:00</div>
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
<div class="recording-controls">
|
|
234
|
-
<button id="startBtn" class="btn btn-primary">
|
|
235
|
-
🎬 Start Recording
|
|
236
|
-
</button>
|
|
237
|
-
<button id="stopBtn" class="btn btn-danger" disabled>
|
|
238
|
-
🛑 Stop Recording
|
|
239
|
-
</button>
|
|
240
|
-
<button id="refreshBtn" class="btn btn-secondary">🔄 Refresh</button>
|
|
241
|
-
<button id="permissionsBtn" class="btn btn-secondary">
|
|
242
|
-
🔐 Check Permissions
|
|
243
|
-
</button>
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
<div class="options-panel">
|
|
247
|
-
<h3>📋 Recording Options</h3>
|
|
248
|
-
<div class="form-group">
|
|
249
|
-
<label>
|
|
250
|
-
<input type="checkbox" id="captureCursor" /> Capture Cursor
|
|
251
|
-
</label>
|
|
252
|
-
</div>
|
|
253
|
-
<div class="form-group">
|
|
254
|
-
<label>
|
|
255
|
-
<input type="checkbox" id="includeMicrophone" /> Include Microphone
|
|
256
|
-
</label>
|
|
257
|
-
</div>
|
|
258
|
-
<div class="form-group">
|
|
259
|
-
<label>
|
|
260
|
-
<input type="checkbox" id="includeSystemAudio" /> Include System
|
|
261
|
-
Audio
|
|
262
|
-
</label>
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
<div class="sections">
|
|
267
|
-
<div>
|
|
268
|
-
<div class="info-section">
|
|
269
|
-
<h3>📺 Displays</h3>
|
|
270
|
-
<div class="displays-grid" id="displaysGrid">
|
|
271
|
-
<div>Loading displays...</div>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
<div class="info-section">
|
|
276
|
-
<h3>🔐 Permissions</h3>
|
|
277
|
-
<div id="permissionsInfo">
|
|
278
|
-
<div>Checking permissions...</div>
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
<div>
|
|
284
|
-
<div class="info-section">
|
|
285
|
-
<h3>🪟 Windows</h3>
|
|
286
|
-
<div class="windows-grid" id="windowsGrid">
|
|
287
|
-
<div>Loading windows...</div>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
<div class="info-section">
|
|
292
|
-
<h3>📜 Activity Log</h3>
|
|
293
|
-
<div class="log-area" id="logArea"></div>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
<script>
|
|
300
|
-
// Application state
|
|
301
|
-
let selectedDisplayId = null;
|
|
302
|
-
let selectedWindowId = null;
|
|
303
|
-
let isRecording = false;
|
|
304
|
-
let recordingStartTime = 0;
|
|
305
|
-
let timerInterval = null;
|
|
306
|
-
|
|
307
|
-
// UI Elements
|
|
308
|
-
const startBtn = document.getElementById("startBtn");
|
|
309
|
-
const stopBtn = document.getElementById("stopBtn");
|
|
310
|
-
const refreshBtn = document.getElementById("refreshBtn");
|
|
311
|
-
const permissionsBtn = document.getElementById("permissionsBtn");
|
|
312
|
-
const recordingStatus = document.getElementById("recordingStatus");
|
|
313
|
-
const recordingTime = document.getElementById("recordingTime");
|
|
314
|
-
const logArea = document.getElementById("logArea");
|
|
315
|
-
const displaysGrid = document.getElementById("displaysGrid");
|
|
316
|
-
const windowsGrid = document.getElementById("windowsGrid");
|
|
317
|
-
const permissionsInfo = document.getElementById("permissionsInfo");
|
|
318
|
-
|
|
319
|
-
// Logging function
|
|
320
|
-
function log(message, type = "info") {
|
|
321
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
322
|
-
const logEntry = `[${timestamp}] ${message}\n`;
|
|
323
|
-
logArea.textContent += logEntry;
|
|
324
|
-
logArea.scrollTop = logArea.scrollHeight;
|
|
325
|
-
console.log(message);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Format time as MM:SS
|
|
329
|
-
function formatTime(seconds) {
|
|
330
|
-
const mins = Math.floor(seconds / 60);
|
|
331
|
-
const secs = seconds % 60;
|
|
332
|
-
return `${mins.toString().padStart(2, "0")}:${secs
|
|
333
|
-
.toString()
|
|
334
|
-
.padStart(2, "0")}`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Update recording timer
|
|
338
|
-
function updateTimer() {
|
|
339
|
-
if (isRecording) {
|
|
340
|
-
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
|
|
341
|
-
recordingTime.textContent = formatTime(elapsed);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Event Listeners Setup
|
|
346
|
-
window.electronAPI.recorder.onRecordingStarted((event, data) => {
|
|
347
|
-
log("🎬 Recording started successfully");
|
|
348
|
-
isRecording = true;
|
|
349
|
-
recordingStartTime = Date.now();
|
|
350
|
-
recordingStatus.textContent = "Recording";
|
|
351
|
-
startBtn.disabled = true;
|
|
352
|
-
stopBtn.disabled = false;
|
|
353
|
-
|
|
354
|
-
timerInterval = setInterval(updateTimer, 1000);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
window.electronAPI.recorder.onRecordingStopped((event, result) => {
|
|
358
|
-
log("🛑 Recording stopped");
|
|
359
|
-
isRecording = false;
|
|
360
|
-
recordingStatus.textContent = "Stopped";
|
|
361
|
-
startBtn.disabled = false;
|
|
362
|
-
stopBtn.disabled = true;
|
|
363
|
-
|
|
364
|
-
if (timerInterval) {
|
|
365
|
-
clearInterval(timerInterval);
|
|
366
|
-
timerInterval = null;
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
window.electronAPI.recorder.onRecordingCompleted((event, outputPath) => {
|
|
371
|
-
log(`✅ Recording completed: ${outputPath}`);
|
|
372
|
-
recordingStatus.textContent = "Completed";
|
|
373
|
-
recordingTime.textContent = "00:00";
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
window.electronAPI.recorder.onTimeUpdate((event, elapsed) => {
|
|
377
|
-
recordingTime.textContent = formatTime(elapsed);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Button Event Handlers
|
|
381
|
-
startBtn.addEventListener("click", async () => {
|
|
382
|
-
try {
|
|
383
|
-
const saveDialog = await window.electronAPI.dialog.showSaveDialog();
|
|
384
|
-
if (saveDialog.canceled) {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const options = {
|
|
389
|
-
captureCursor: document.getElementById("captureCursor").checked,
|
|
390
|
-
includeMicrophone:
|
|
391
|
-
document.getElementById("includeMicrophone").checked,
|
|
392
|
-
includeSystemAudio:
|
|
393
|
-
document.getElementById("includeSystemAudio").checked,
|
|
394
|
-
displayId: selectedDisplayId,
|
|
395
|
-
windowId: selectedWindowId,
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
log(`🎬 Starting recording with options: ${JSON.stringify(options)}`);
|
|
399
|
-
const result = await window.electronAPI.recorder.startRecording(
|
|
400
|
-
saveDialog.filePath,
|
|
401
|
-
options
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
if (!result.success) {
|
|
405
|
-
log(`❌ Failed to start recording: ${result.error}`, "error");
|
|
406
|
-
}
|
|
407
|
-
} catch (error) {
|
|
408
|
-
log(`❌ Error starting recording: ${error.message}`, "error");
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
stopBtn.addEventListener("click", async () => {
|
|
413
|
-
try {
|
|
414
|
-
log("🛑 Stopping recording...");
|
|
415
|
-
const result = await window.electronAPI.recorder.stopRecording();
|
|
416
|
-
|
|
417
|
-
if (!result.success) {
|
|
418
|
-
log(`❌ Failed to stop recording: ${result.error}`, "error");
|
|
419
|
-
}
|
|
420
|
-
} catch (error) {
|
|
421
|
-
log(`❌ Error stopping recording: ${error.message}`, "error");
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
refreshBtn.addEventListener("click", () => {
|
|
426
|
-
log("🔄 Refreshing...");
|
|
427
|
-
loadDisplays();
|
|
428
|
-
loadWindows();
|
|
429
|
-
checkPermissions();
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
permissionsBtn.addEventListener("click", checkPermissions);
|
|
433
|
-
|
|
434
|
-
// Load displays
|
|
435
|
-
async function loadDisplays() {
|
|
436
|
-
try {
|
|
437
|
-
const displays = await window.electronAPI.recorder.getDisplays();
|
|
438
|
-
displaysGrid.innerHTML = "";
|
|
439
|
-
|
|
440
|
-
for (const display of displays) {
|
|
441
|
-
const displayDiv = document.createElement("div");
|
|
442
|
-
displayDiv.className = "display-item";
|
|
443
|
-
displayDiv.onclick = () => selectDisplay(display.id, displayDiv);
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const thumbnail =
|
|
447
|
-
await window.electronAPI.recorder.getDisplayThumbnail(
|
|
448
|
-
display.id,
|
|
449
|
-
{ maxWidth: 200, maxHeight: 120 }
|
|
450
|
-
);
|
|
451
|
-
displayDiv.innerHTML = `
|
|
452
|
-
<img src="${thumbnail}" class="thumbnail" alt="Display ${
|
|
453
|
-
display.id
|
|
454
|
-
}">
|
|
455
|
-
<div><strong>${display.name}</strong></div>
|
|
456
|
-
<div>${display.width}x${display.height}</div>
|
|
457
|
-
<div>${
|
|
458
|
-
display.isPrimary ? "🖥️ Primary" : "📱 Secondary"
|
|
459
|
-
}</div>
|
|
460
|
-
`;
|
|
461
|
-
} catch (error) {
|
|
462
|
-
displayDiv.innerHTML = `
|
|
463
|
-
<div class="thumbnail"></div>
|
|
464
|
-
<div><strong>${display.name}</strong></div>
|
|
465
|
-
<div>${display.width}x${display.height}</div>
|
|
466
|
-
<div>${
|
|
467
|
-
display.isPrimary ? "🖥️ Primary" : "📱 Secondary"
|
|
468
|
-
}</div>
|
|
469
|
-
`;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
displaysGrid.appendChild(displayDiv);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
log(`📺 Loaded ${displays.length} displays`);
|
|
476
|
-
} catch (error) {
|
|
477
|
-
log(`❌ Error loading displays: ${error.message}`, "error");
|
|
478
|
-
displaysGrid.innerHTML = "<div>Failed to load displays</div>";
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Load windows
|
|
483
|
-
async function loadWindows() {
|
|
484
|
-
try {
|
|
485
|
-
const windows = await window.electronAPI.recorder.getWindows();
|
|
486
|
-
windowsGrid.innerHTML = "";
|
|
487
|
-
|
|
488
|
-
for (const window of windows) {
|
|
489
|
-
const windowDiv = document.createElement("div");
|
|
490
|
-
windowDiv.className = "window-item";
|
|
491
|
-
windowDiv.onclick = () => selectWindow(window.id, windowDiv);
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
const thumbnail =
|
|
495
|
-
await window.electronAPI.recorder.getWindowThumbnail(
|
|
496
|
-
window.id,
|
|
497
|
-
{ maxWidth: 200, maxHeight: 120 }
|
|
498
|
-
);
|
|
499
|
-
windowDiv.innerHTML = `
|
|
500
|
-
<img src="${thumbnail}" class="thumbnail" alt="${window.name}">
|
|
501
|
-
<div><strong>${window.name}</strong></div>
|
|
502
|
-
<div>${window.appName}</div>
|
|
503
|
-
<div>${window.width}x${window.height}</div>
|
|
504
|
-
`;
|
|
505
|
-
} catch (error) {
|
|
506
|
-
windowDiv.innerHTML = `
|
|
507
|
-
<div class="thumbnail"></div>
|
|
508
|
-
<div><strong>${window.name}</strong></div>
|
|
509
|
-
<div>${window.appName}</div>
|
|
510
|
-
<div>${window.width}x${window.height}</div>
|
|
511
|
-
`;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
windowsGrid.appendChild(windowDiv);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
log(`🪟 Loaded ${windows.length} windows`);
|
|
518
|
-
} catch (error) {
|
|
519
|
-
log(`❌ Error loading windows: ${error.message}`, "error");
|
|
520
|
-
windowsGrid.innerHTML = "<div>Failed to load windows</div>";
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Check permissions
|
|
525
|
-
async function checkPermissions() {
|
|
526
|
-
try {
|
|
527
|
-
const permissions =
|
|
528
|
-
await window.electronAPI.recorder.checkPermissions();
|
|
529
|
-
|
|
530
|
-
permissionsInfo.innerHTML = `
|
|
531
|
-
<div class="permission-item">
|
|
532
|
-
<span>Screen Recording:</span>
|
|
533
|
-
<span class="status-indicator ${
|
|
534
|
-
permissions.screenRecording
|
|
535
|
-
? "status-granted"
|
|
536
|
-
: "status-denied"
|
|
537
|
-
}">
|
|
538
|
-
${
|
|
539
|
-
permissions.screenRecording ? "Granted" : "Denied"
|
|
540
|
-
}
|
|
541
|
-
</span>
|
|
542
|
-
</div>
|
|
543
|
-
<div class="permission-item">
|
|
544
|
-
<span>Accessibility:</span>
|
|
545
|
-
<span class="status-indicator ${
|
|
546
|
-
permissions.accessibility
|
|
547
|
-
? "status-granted"
|
|
548
|
-
: "status-denied"
|
|
549
|
-
}">
|
|
550
|
-
${permissions.accessibility ? "Granted" : "Denied"}
|
|
551
|
-
</span>
|
|
552
|
-
</div>
|
|
553
|
-
<div class="permission-item">
|
|
554
|
-
<span>Microphone:</span>
|
|
555
|
-
<span class="status-indicator ${
|
|
556
|
-
permissions.microphone
|
|
557
|
-
? "status-granted"
|
|
558
|
-
: "status-denied"
|
|
559
|
-
}">
|
|
560
|
-
${permissions.microphone ? "Granted" : "Denied"}
|
|
561
|
-
</span>
|
|
562
|
-
</div>
|
|
563
|
-
`;
|
|
564
|
-
|
|
565
|
-
log("🔐 Permissions checked");
|
|
566
|
-
} catch (error) {
|
|
567
|
-
log(`❌ Error checking permissions: ${error.message}`, "error");
|
|
568
|
-
permissionsInfo.innerHTML = "<div>Failed to check permissions</div>";
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Select display
|
|
573
|
-
function selectDisplay(displayId, element) {
|
|
574
|
-
// Clear previous selections
|
|
575
|
-
document
|
|
576
|
-
.querySelectorAll(".display-item")
|
|
577
|
-
.forEach((item) => item.classList.remove("selected"));
|
|
578
|
-
document
|
|
579
|
-
.querySelectorAll(".window-item")
|
|
580
|
-
.forEach((item) => item.classList.remove("selected"));
|
|
581
|
-
|
|
582
|
-
selectedDisplayId = displayId;
|
|
583
|
-
selectedWindowId = null;
|
|
584
|
-
element.classList.add("selected");
|
|
585
|
-
log(`📺 Selected display: ${displayId}`);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Select window
|
|
589
|
-
function selectWindow(windowId, element) {
|
|
590
|
-
// Clear previous selections
|
|
591
|
-
document
|
|
592
|
-
.querySelectorAll(".display-item")
|
|
593
|
-
.forEach((item) => item.classList.remove("selected"));
|
|
594
|
-
document
|
|
595
|
-
.querySelectorAll(".window-item")
|
|
596
|
-
.forEach((item) => item.classList.remove("selected"));
|
|
597
|
-
|
|
598
|
-
selectedWindowId = windowId;
|
|
599
|
-
selectedDisplayId = null;
|
|
600
|
-
element.classList.add("selected");
|
|
601
|
-
log(`🪟 Selected window: ${windowId}`);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Initialize app
|
|
605
|
-
async function init() {
|
|
606
|
-
try {
|
|
607
|
-
const moduleInfo = await window.electronAPI.recorder.getModuleInfo();
|
|
608
|
-
log(
|
|
609
|
-
`🔌 Module initialized: ${moduleInfo.version} (Electron-safe: ${moduleInfo.electronSafe})`
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
await loadDisplays();
|
|
613
|
-
await loadWindows();
|
|
614
|
-
await checkPermissions();
|
|
615
|
-
|
|
616
|
-
log("✅ Application initialized successfully");
|
|
617
|
-
} catch (error) {
|
|
618
|
-
log(`❌ Initialization error: ${error.message}`, "error");
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Start the app
|
|
623
|
-
init();
|
|
624
|
-
|
|
625
|
-
// Cleanup on page unload
|
|
626
|
-
window.addEventListener("beforeunload", () => {
|
|
627
|
-
if (timerInterval) {
|
|
628
|
-
clearInterval(timerInterval);
|
|
629
|
-
}
|
|
630
|
-
window.electronAPI.recorder.removeAllListeners();
|
|
631
|
-
});
|
|
632
|
-
</script>
|
|
633
|
-
</body>
|
|
634
|
-
</html>
|