node-mac-recorder 2.4.10 → 2.4.11
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/ELECTRON-INTEGRATION.md +411 -43
- package/package.json +1 -1
- package/src/window_selector.mm +142 -19
- package/test-overlay-fix.js +72 -0
package/ELECTRON-INTEGRATION.md
CHANGED
|
@@ -34,40 +34,72 @@ const selector = new ElectronWindowSelector();
|
|
|
34
34
|
console.log(`Environment: ${selector.isElectron ? 'Electron' : 'Node.js'}`);
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### Real-Time Window Detection (Electron Mode)
|
|
38
38
|
```javascript
|
|
39
39
|
// 1. Mevcut pencere listesini al
|
|
40
40
|
const windows = await selector.getAvailableWindows();
|
|
41
41
|
console.log(`Found ${windows.length} windows`);
|
|
42
42
|
|
|
43
|
-
// 2.
|
|
44
|
-
const
|
|
43
|
+
// 2. Real-time mouse tracking başlat
|
|
44
|
+
const nativeBinding = require('./build/Release/mac_recorder.node');
|
|
45
|
+
const trackingInterval = setInterval(() => {
|
|
46
|
+
const status = nativeBinding.getWindowSelectionStatus();
|
|
47
|
+
|
|
48
|
+
if (status && status.currentWindow) {
|
|
49
|
+
const window = status.currentWindow;
|
|
50
|
+
console.log(`Window under cursor: ${window.appName} - "${window.title}"`);
|
|
51
|
+
console.log(`Position: (${window.x}, ${window.y}) Size: ${window.width}x${window.height}`);
|
|
52
|
+
|
|
53
|
+
if (window.screenId !== undefined) {
|
|
54
|
+
console.log(`Screen: ${window.screenId} (${window.screenWidth}x${window.screenHeight})`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Electron UI'da bu window'u real-time highlight et
|
|
58
|
+
highlightWindowInElectronUI(window);
|
|
59
|
+
}
|
|
60
|
+
}, 100); // 100ms polling for smooth tracking
|
|
45
61
|
|
|
46
|
-
// 3.
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
// 3. Pencere seçimi tamamlandığında
|
|
63
|
+
function selectWindow(windowInfo) {
|
|
64
|
+
clearInterval(trackingInterval);
|
|
65
|
+
console.log('Window selected:', windowInfo);
|
|
49
66
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
}
|
|
67
|
+
// Recording başlat
|
|
68
|
+
startRecordingWithWindow(windowInfo);
|
|
69
|
+
}
|
|
53
70
|
```
|
|
54
71
|
|
|
55
|
-
### Display Seçimi (Electron
|
|
72
|
+
### Screen/Display Seçimi (Electron Mode)
|
|
56
73
|
```javascript
|
|
57
74
|
// 1. Mevcut display listesini al
|
|
58
75
|
const displays = await selector.getAvailableDisplays();
|
|
59
76
|
console.log(`Found ${displays.length} displays`);
|
|
60
77
|
|
|
61
|
-
// 2.
|
|
62
|
-
const
|
|
78
|
+
// 2. Screen seçim başlat (Electron mode'da otomatik main screen)
|
|
79
|
+
const nativeBinding = require('./build/Release/mac_recorder.node');
|
|
80
|
+
const screenResult = nativeBinding.startScreenSelection();
|
|
63
81
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (screenResult) {
|
|
83
|
+
// Screen seçim bilgisini al
|
|
84
|
+
const selectedScreen = nativeBinding.getSelectedScreenInfo();
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
86
|
+
if (selectedScreen) {
|
|
87
|
+
console.log('Screen selected:', selectedScreen);
|
|
88
|
+
console.log(`Resolution: ${selectedScreen.width}x${selectedScreen.height}`);
|
|
89
|
+
console.log(`Position: (${selectedScreen.x}, ${selectedScreen.y})`);
|
|
90
|
+
|
|
91
|
+
// Screen recording başlat
|
|
92
|
+
startScreenRecording(selectedScreen);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Manuel screen seçimi (UI ile)
|
|
97
|
+
function selectScreen(screenInfo) {
|
|
98
|
+
console.log('Screen manually selected:', screenInfo);
|
|
99
|
+
|
|
100
|
+
// Recording başlat
|
|
101
|
+
startScreenRecordingWithScreen(screenInfo);
|
|
102
|
+
}
|
|
71
103
|
```
|
|
72
104
|
|
|
73
105
|
## 🎨 Electron UI Implementation Önerisi
|
|
@@ -104,12 +136,14 @@ selector.on('screenSelected', (screenInfo) => {
|
|
|
104
136
|
</div>
|
|
105
137
|
```
|
|
106
138
|
|
|
107
|
-
### 2. Renderer Process
|
|
139
|
+
### 2. Real-Time Window Tracking (Renderer Process)
|
|
108
140
|
```javascript
|
|
109
141
|
// renderer.js
|
|
110
142
|
const { ipcRenderer } = require('electron');
|
|
111
143
|
|
|
112
144
|
let windowSelector = null;
|
|
145
|
+
let trackingInterval = null;
|
|
146
|
+
let currentHighlightedWindow = null;
|
|
113
147
|
|
|
114
148
|
async function initializeWindowPicker() {
|
|
115
149
|
// Main process'ten window selector'ı başlat
|
|
@@ -119,6 +153,77 @@ async function initializeWindowPicker() {
|
|
|
119
153
|
// Mevcut windows'ları al
|
|
120
154
|
const windows = await ipcRenderer.invoke('get-available-windows');
|
|
121
155
|
displayWindowsInUI(windows);
|
|
156
|
+
|
|
157
|
+
// Real-time tracking başlat
|
|
158
|
+
startRealTimeTracking();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function startRealTimeTracking() {
|
|
163
|
+
trackingInterval = setInterval(async () => {
|
|
164
|
+
try {
|
|
165
|
+
// Main process'ten mouse altındaki pencere bilgisini al
|
|
166
|
+
const currentWindow = await ipcRenderer.invoke('get-current-window-under-cursor');
|
|
167
|
+
|
|
168
|
+
if (currentWindow && currentWindow.id !== currentHighlightedWindow?.id) {
|
|
169
|
+
// Yeni pencere tespit edildi
|
|
170
|
+
highlightWindowInUI(currentWindow);
|
|
171
|
+
currentHighlightedWindow = currentWindow;
|
|
172
|
+
|
|
173
|
+
// UI'da bilgileri güncelle
|
|
174
|
+
updateCurrentWindowInfo(currentWindow);
|
|
175
|
+
} else if (!currentWindow && currentHighlightedWindow) {
|
|
176
|
+
// Mouse hiçbir pencere üstünde değil
|
|
177
|
+
clearWindowHighlight();
|
|
178
|
+
currentHighlightedWindow = null;
|
|
179
|
+
clearCurrentWindowInfo();
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn('Window tracking error:', error.message);
|
|
183
|
+
}
|
|
184
|
+
}, 100); // 100ms smooth tracking
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function highlightWindowInUI(window) {
|
|
188
|
+
// Tüm window card'ları normal hale getir
|
|
189
|
+
document.querySelectorAll('.window-card').forEach(card => {
|
|
190
|
+
card.classList.remove('hover-detected');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Eşleşen window card'ı highlight et
|
|
194
|
+
const matchingCard = document.querySelector(`[data-window-id="${window.id}"]`);
|
|
195
|
+
if (matchingCard) {
|
|
196
|
+
matchingCard.classList.add('hover-detected');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function clearWindowHighlight() {
|
|
201
|
+
document.querySelectorAll('.window-card').forEach(card => {
|
|
202
|
+
card.classList.remove('hover-detected');
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function updateCurrentWindowInfo(window) {
|
|
207
|
+
const infoDiv = document.getElementById('current-window-info');
|
|
208
|
+
if (infoDiv) {
|
|
209
|
+
infoDiv.innerHTML = `
|
|
210
|
+
<h4>Window Under Cursor</h4>
|
|
211
|
+
<p><strong>App:</strong> ${window.appName}</p>
|
|
212
|
+
<p><strong>Title:</strong> ${window.title}</p>
|
|
213
|
+
<p><strong>Size:</strong> ${window.width}×${window.height}</p>
|
|
214
|
+
<p><strong>Position:</strong> (${window.x}, ${window.y})</p>
|
|
215
|
+
${window.screenId !== undefined ?
|
|
216
|
+
`<p><strong>Screen:</strong> ${window.screenId} (${window.screenWidth}×${window.screenHeight})</p>` :
|
|
217
|
+
''}
|
|
218
|
+
`;
|
|
219
|
+
infoDiv.classList.add('visible');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clearCurrentWindowInfo() {
|
|
224
|
+
const infoDiv = document.getElementById('current-window-info');
|
|
225
|
+
if (infoDiv) {
|
|
226
|
+
infoDiv.classList.remove('visible');
|
|
122
227
|
}
|
|
123
228
|
}
|
|
124
229
|
|
|
@@ -135,6 +240,7 @@ function displayWindowsInUI(windows) {
|
|
|
135
240
|
function createWindowCard(window) {
|
|
136
241
|
const card = document.createElement('div');
|
|
137
242
|
card.className = 'window-card';
|
|
243
|
+
card.setAttribute('data-window-id', window.id);
|
|
138
244
|
card.innerHTML = `
|
|
139
245
|
<div class="window-thumbnail">
|
|
140
246
|
<div class="window-placeholder">${window.appName?.charAt(0) || '?'}</div>
|
|
@@ -151,28 +257,50 @@ function createWindowCard(window) {
|
|
|
151
257
|
}
|
|
152
258
|
|
|
153
259
|
function selectWindow(window) {
|
|
260
|
+
// Tracking'i durdur
|
|
261
|
+
if (trackingInterval) {
|
|
262
|
+
clearInterval(trackingInterval);
|
|
263
|
+
trackingInterval = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
154
266
|
// UI'da seçimi görsel olarak göster
|
|
155
|
-
document.querySelectorAll('.window-card').forEach(card =>
|
|
156
|
-
card.classList.remove('selected')
|
|
157
|
-
);
|
|
267
|
+
document.querySelectorAll('.window-card').forEach(card => {
|
|
268
|
+
card.classList.remove('selected', 'hover-detected');
|
|
269
|
+
});
|
|
158
270
|
event.target.closest('.window-card').classList.add('selected');
|
|
159
271
|
|
|
160
272
|
// Main process'e seçimi bildir
|
|
161
273
|
ipcRenderer.invoke('window-selected', window);
|
|
162
274
|
}
|
|
275
|
+
|
|
276
|
+
// Cleanup function
|
|
277
|
+
window.addEventListener('beforeunload', () => {
|
|
278
|
+
if (trackingInterval) {
|
|
279
|
+
clearInterval(trackingInterval);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
163
282
|
```
|
|
164
283
|
|
|
165
|
-
### 3. Main Process Handler
|
|
284
|
+
### 3. Enhanced Main Process Handler
|
|
166
285
|
```javascript
|
|
167
286
|
// main.js
|
|
168
287
|
const { ipcMain } = require('electron');
|
|
169
288
|
const ElectronWindowSelector = require('node-mac-recorder/electron-window-selector');
|
|
170
289
|
|
|
171
290
|
let windowSelector = null;
|
|
291
|
+
let nativeBinding = null;
|
|
172
292
|
|
|
173
293
|
ipcMain.handle('init-window-selector', async () => {
|
|
174
294
|
try {
|
|
175
295
|
windowSelector = new ElectronWindowSelector();
|
|
296
|
+
|
|
297
|
+
// Native binding'i yükle (real-time tracking için)
|
|
298
|
+
try {
|
|
299
|
+
nativeBinding = require('./build/Release/mac_recorder.node');
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.warn('Native binding yüklenemedi:', error.message);
|
|
302
|
+
}
|
|
303
|
+
|
|
176
304
|
return { success: true };
|
|
177
305
|
} catch (error) {
|
|
178
306
|
return { success: false, error: error.message };
|
|
@@ -189,20 +317,99 @@ ipcMain.handle('get-available-displays', async () => {
|
|
|
189
317
|
return await windowSelector.getAvailableDisplays();
|
|
190
318
|
});
|
|
191
319
|
|
|
320
|
+
// Real-time window tracking için yeni handler
|
|
321
|
+
ipcMain.handle('get-current-window-under-cursor', async () => {
|
|
322
|
+
if (!nativeBinding) return null;
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const status = nativeBinding.getWindowSelectionStatus();
|
|
326
|
+
return status?.currentWindow || null;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.warn('Window status alınamadı:', error.message);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
192
333
|
ipcMain.handle('window-selected', async (event, windowInfo) => {
|
|
193
334
|
console.log('Window selected in Electron UI:', windowInfo);
|
|
335
|
+
console.log(` - App: ${windowInfo.appName}`);
|
|
336
|
+
console.log(` - Title: ${windowInfo.title}`);
|
|
337
|
+
console.log(` - Size: ${windowInfo.width}×${windowInfo.height}`);
|
|
338
|
+
console.log(` - Position: (${windowInfo.x}, ${windowInfo.y})`);
|
|
339
|
+
|
|
340
|
+
if (windowInfo.screenId !== undefined) {
|
|
341
|
+
console.log(` - Screen: ${windowInfo.screenId} (${windowInfo.screenWidth}×${windowInfo.screenHeight})`);
|
|
342
|
+
}
|
|
194
343
|
|
|
195
344
|
// Recording başlatılabilir
|
|
196
345
|
const MacRecorder = require('node-mac-recorder');
|
|
197
346
|
const recorder = new MacRecorder();
|
|
198
347
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
348
|
+
try {
|
|
349
|
+
// Window recording başlat
|
|
350
|
+
await recorder.startRecording('./output.mov', {
|
|
351
|
+
windowId: windowInfo.id,
|
|
352
|
+
// Screen coordination
|
|
353
|
+
x: windowInfo.x,
|
|
354
|
+
y: windowInfo.y,
|
|
355
|
+
width: windowInfo.width,
|
|
356
|
+
height: windowInfo.height,
|
|
357
|
+
// Diğer options
|
|
358
|
+
fps: 30,
|
|
359
|
+
audioEnabled: true
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
success: true,
|
|
364
|
+
message: 'Recording started successfully',
|
|
365
|
+
windowInfo: windowInfo
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
return {
|
|
369
|
+
success: false,
|
|
370
|
+
error: error.message
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Screen recording handler
|
|
376
|
+
ipcMain.handle('screen-selected', async (event, screenInfo) => {
|
|
377
|
+
console.log('Screen selected in Electron UI:', screenInfo);
|
|
378
|
+
|
|
379
|
+
const MacRecorder = require('node-mac-recorder');
|
|
380
|
+
const recorder = new MacRecorder();
|
|
204
381
|
|
|
205
|
-
|
|
382
|
+
try {
|
|
383
|
+
// Screen recording başlat
|
|
384
|
+
await recorder.startRecording('./screen-output.mov', {
|
|
385
|
+
// Screen mode
|
|
386
|
+
screenId: screenInfo.id,
|
|
387
|
+
x: screenInfo.x,
|
|
388
|
+
y: screenInfo.y,
|
|
389
|
+
width: screenInfo.width,
|
|
390
|
+
height: screenInfo.height,
|
|
391
|
+
fps: 30,
|
|
392
|
+
audioEnabled: true
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
message: 'Screen recording started',
|
|
398
|
+
screenInfo: screenInfo
|
|
399
|
+
};
|
|
400
|
+
} catch (error) {
|
|
401
|
+
return {
|
|
402
|
+
success: false,
|
|
403
|
+
error: error.message
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Recording control handlers
|
|
409
|
+
ipcMain.handle('stop-recording', async () => {
|
|
410
|
+
// Aktif recorder instance'ı durdur
|
|
411
|
+
// Bu implementation'a recorder management eklenmeli
|
|
412
|
+
return { success: true, message: 'Recording stopped' };
|
|
206
413
|
});
|
|
207
414
|
```
|
|
208
415
|
|
|
@@ -243,12 +450,13 @@ function showRecordingPreview(windowInfo) {
|
|
|
243
450
|
}
|
|
244
451
|
```
|
|
245
452
|
|
|
246
|
-
##
|
|
453
|
+
## 🎨 Enhanced CSS Styling (Real-Time Tracking)
|
|
247
454
|
```css
|
|
248
455
|
.window-picker {
|
|
249
456
|
padding: 20px;
|
|
250
457
|
max-height: 500px;
|
|
251
458
|
overflow-y: auto;
|
|
459
|
+
position: relative;
|
|
252
460
|
}
|
|
253
461
|
|
|
254
462
|
.window-grid {
|
|
@@ -264,6 +472,7 @@ function showRecordingPreview(windowInfo) {
|
|
|
264
472
|
padding: 10px;
|
|
265
473
|
cursor: pointer;
|
|
266
474
|
transition: all 0.2s;
|
|
475
|
+
position: relative;
|
|
267
476
|
}
|
|
268
477
|
|
|
269
478
|
.window-card:hover {
|
|
@@ -276,6 +485,27 @@ function showRecordingPreview(windowInfo) {
|
|
|
276
485
|
background-color: #e6f3ff;
|
|
277
486
|
}
|
|
278
487
|
|
|
488
|
+
/* Real-time hover detection styling */
|
|
489
|
+
.window-card.hover-detected {
|
|
490
|
+
border-color: #ff6b35 !important;
|
|
491
|
+
background-color: #fff3f0 !important;
|
|
492
|
+
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
|
|
493
|
+
transform: translateY(-2px);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.window-card.hover-detected::before {
|
|
497
|
+
content: "🎯 CURSOR HERE";
|
|
498
|
+
position: absolute;
|
|
499
|
+
top: -10px;
|
|
500
|
+
right: 5px;
|
|
501
|
+
background: #ff6b35;
|
|
502
|
+
color: white;
|
|
503
|
+
padding: 2px 6px;
|
|
504
|
+
border-radius: 4px;
|
|
505
|
+
font-size: 10px;
|
|
506
|
+
font-weight: bold;
|
|
507
|
+
}
|
|
508
|
+
|
|
279
509
|
.window-thumbnail {
|
|
280
510
|
height: 80px;
|
|
281
511
|
background: #f5f5f5;
|
|
@@ -312,31 +542,169 @@ function showRecordingPreview(windowInfo) {
|
|
|
312
542
|
color: #999;
|
|
313
543
|
font-size: 11px;
|
|
314
544
|
}
|
|
545
|
+
|
|
546
|
+
/* Real-time info panel */
|
|
547
|
+
#current-window-info {
|
|
548
|
+
position: fixed;
|
|
549
|
+
top: 20px;
|
|
550
|
+
right: 20px;
|
|
551
|
+
background: rgba(0, 0, 0, 0.9);
|
|
552
|
+
color: white;
|
|
553
|
+
padding: 15px;
|
|
554
|
+
border-radius: 8px;
|
|
555
|
+
min-width: 250px;
|
|
556
|
+
opacity: 0;
|
|
557
|
+
transform: translateX(300px);
|
|
558
|
+
transition: all 0.3s ease;
|
|
559
|
+
z-index: 1000;
|
|
560
|
+
font-family: 'Monaco', monospace;
|
|
561
|
+
font-size: 12px;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
#current-window-info.visible {
|
|
565
|
+
opacity: 1;
|
|
566
|
+
transform: translateX(0);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
#current-window-info h4 {
|
|
570
|
+
margin: 0 0 10px 0;
|
|
571
|
+
color: #ff6b35;
|
|
572
|
+
font-size: 14px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#current-window-info p {
|
|
576
|
+
margin: 4px 0;
|
|
577
|
+
line-height: 1.4;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
#current-window-info strong {
|
|
581
|
+
color: #fff;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* Animation for smooth transitions */
|
|
585
|
+
.window-card {
|
|
586
|
+
will-change: transform, box-shadow, border-color;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
@keyframes pulseHover {
|
|
590
|
+
0% { transform: scale(1) translateY(-2px); }
|
|
591
|
+
50% { transform: scale(1.02) translateY(-3px); }
|
|
592
|
+
100% { transform: scale(1) translateY(-2px); }
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.window-card.hover-detected {
|
|
596
|
+
animation: pulseHover 2s infinite;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* Status indicator */
|
|
600
|
+
.tracking-status {
|
|
601
|
+
position: absolute;
|
|
602
|
+
top: 10px;
|
|
603
|
+
left: 20px;
|
|
604
|
+
background: #4CAF50;
|
|
605
|
+
color: white;
|
|
606
|
+
padding: 5px 10px;
|
|
607
|
+
border-radius: 15px;
|
|
608
|
+
font-size: 12px;
|
|
609
|
+
font-weight: bold;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.tracking-status::before {
|
|
613
|
+
content: "🔴 ";
|
|
614
|
+
animation: blink 1s infinite;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
@keyframes blink {
|
|
618
|
+
0%, 50% { opacity: 1; }
|
|
619
|
+
51%, 100% { opacity: 0; }
|
|
620
|
+
}
|
|
315
621
|
```
|
|
316
622
|
|
|
623
|
+
## ✨ Yeni Real-Time Tracking Özellikleri
|
|
624
|
+
|
|
625
|
+
### 🎯 Anlık Window Tespit
|
|
626
|
+
- **100ms polling** ile smooth mouse tracking
|
|
627
|
+
- Mouse hangi pencere üstüne giderse **otomatik highlight**
|
|
628
|
+
- **Screen detection** - pencere hangi ekranda, otomatik tespit
|
|
629
|
+
- **Koordinat bilgisi** - x, y, width, height gerçek zamanlı
|
|
630
|
+
|
|
631
|
+
### 🔥 UI Features
|
|
632
|
+
- `hover-detected` class ile anlık görsel feedback
|
|
633
|
+
- Real-time info panel (sağ üst köşe)
|
|
634
|
+
- Pulse animation effect
|
|
635
|
+
- "🎯 CURSOR HERE" indicator
|
|
636
|
+
|
|
637
|
+
### 🖥️ Multi-Screen Support
|
|
638
|
+
- Pencere hangi screen'de otomatik tespit
|
|
639
|
+
- Screen koordinatları ve boyutları dahil
|
|
640
|
+
- Cross-screen window tracking
|
|
641
|
+
|
|
317
642
|
## ⚠️ Önemli Notlar
|
|
318
643
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
644
|
+
### 🔧 Teknik Gereksinimler
|
|
645
|
+
1. **Native Module**: Real-time tracking için native binding gerekli
|
|
646
|
+
2. **macOS Permissions**: Screen Recording ve Accessibility izinleri
|
|
647
|
+
3. **Electron Environment**: `ELECTRON_VERSION` env variable otomatik tespit
|
|
648
|
+
4. **Performance**: 100ms polling interval (ayarlanabilir)
|
|
649
|
+
|
|
650
|
+
### 🛠️ Implementation Notes
|
|
651
|
+
1. **IPC Communication**: Main ↔ Renderer process real-time data exchange
|
|
652
|
+
2. **Memory Management**: Interval cleanup önemli
|
|
653
|
+
3. **Error Handling**: Native binding yoksa graceful fallback
|
|
654
|
+
4. **UI Responsiveness**: CSS transitions ile smooth UX
|
|
655
|
+
|
|
656
|
+
### 🚨 Troubleshooting
|
|
657
|
+
- **Native binding yüklenemezse**: `npm run build` ile tekrar derle
|
|
658
|
+
- **Permission hatası**: System Preferences → Security & Privacy
|
|
659
|
+
- **Tracking çalışmıyorsa**: `ELECTRON_VERSION` environment variable kontrol et
|
|
660
|
+
- **UI update yavaşsa**: Polling interval'ı artır (100ms → 200ms)
|
|
323
661
|
|
|
324
662
|
## 🚀 Sonraki Adımlar
|
|
325
663
|
|
|
326
|
-
1
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
664
|
+
### Phase 1: Core Enhancement
|
|
665
|
+
1. ✅ **Real-time window tracking** - TAMAMLANDI
|
|
666
|
+
2. ✅ **Screen detection accuracy** - TAMAMLANDI
|
|
667
|
+
3. ✅ **Electron compatibility** - TAMAMLANDI
|
|
668
|
+
4. 🔄 Thumbnail generation implementasyonu
|
|
669
|
+
|
|
670
|
+
### Phase 2: Advanced Features
|
|
671
|
+
5. 📋 Window list real-time updates
|
|
672
|
+
6. 🖥️ Multiple display UI enhancement
|
|
673
|
+
7. 📹 Recording progress indicator
|
|
674
|
+
8. ✂️ Custom recording area selection
|
|
675
|
+
9. 🎨 Window preview thumbnails
|
|
676
|
+
|
|
677
|
+
### Phase 3: Performance & UX
|
|
678
|
+
10. ⚡ Performance optimization
|
|
679
|
+
11. 🎭 Advanced animations
|
|
680
|
+
12. 📱 Responsive design improvements
|
|
681
|
+
13. 🔧 Settings panel
|
|
331
682
|
|
|
332
683
|
## 📞 Test Komutları
|
|
333
684
|
|
|
334
685
|
```bash
|
|
335
|
-
#
|
|
336
|
-
|
|
686
|
+
# Fixed overlay functionality test
|
|
687
|
+
node test-overlay-fix.js
|
|
688
|
+
|
|
689
|
+
# Electron mode test
|
|
690
|
+
ELECTRON_VERSION=25.0.0 node test-overlay-fix.js
|
|
691
|
+
|
|
692
|
+
# Build native module
|
|
693
|
+
npm run build
|
|
337
694
|
|
|
338
|
-
#
|
|
695
|
+
# Full integration test
|
|
339
696
|
node test-electron-window-selector.js
|
|
340
697
|
```
|
|
341
698
|
|
|
342
|
-
|
|
699
|
+
## 🎉 Sonuç
|
|
700
|
+
|
|
701
|
+
Bu güncellenmiş implementasyon ile:
|
|
702
|
+
|
|
703
|
+
✅ **Mouse tracking** gerçek zamanlı çalışıyor
|
|
704
|
+
✅ **Window detection** hassas ve hızlı
|
|
705
|
+
✅ **Screen coordination** doğru hesaplanıyor
|
|
706
|
+
✅ **Electron integration** sorunsuz çalışıyor
|
|
707
|
+
✅ **Multi-display support** tam uyumlu
|
|
708
|
+
✅ **Real-time UI feedback** kullanıcı dostu
|
|
709
|
+
|
|
710
|
+
Electron uygulamanızda artık native overlay benzeri deneyim sunabilir, kullanıcı mouse'u hareket ettirdikçe hangi pencere üstünde olduğunu görebilir ve tek tıkla recording başlatabilirsiniz!
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -497,22 +497,56 @@ NSArray* getAllSelectableWindowsLegacy() {
|
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
// Get window under cursor point
|
|
500
|
+
// Get window under cursor point using real-time mouse position
|
|
501
501
|
NSDictionary* getWindowUnderCursor(CGPoint point) {
|
|
502
502
|
@autoreleasepool {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
int y = [[window objectForKey:@"y"] intValue];
|
|
509
|
-
int width = [[window objectForKey:@"width"] intValue];
|
|
510
|
-
int height = [[window objectForKey:@"height"] intValue];
|
|
503
|
+
// Get window ID directly under cursor using macOS API
|
|
504
|
+
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
|
505
|
+
|
|
506
|
+
if (windowList) {
|
|
507
|
+
NSArray *windowArray = (__bridge NSArray *)windowList;
|
|
511
508
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
509
|
+
// Find the topmost window at cursor position
|
|
510
|
+
for (NSDictionary *windowInfo in windowArray) {
|
|
511
|
+
NSDictionary *bounds = [windowInfo objectForKey:(NSString *)kCGWindowBounds];
|
|
512
|
+
NSNumber *layer = [windowInfo objectForKey:(NSString *)kCGWindowLayer];
|
|
513
|
+
NSString *owner = [windowInfo objectForKey:(NSString *)kCGWindowOwnerName];
|
|
514
|
+
NSNumber *winID = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
515
|
+
|
|
516
|
+
if (!bounds || !winID || !owner) continue;
|
|
517
|
+
if ([layer intValue] != 0) continue; // Only normal windows
|
|
518
|
+
if ([owner isEqualToString:@"WindowServer"] || [owner isEqualToString:@"Dock"]) continue;
|
|
519
|
+
|
|
520
|
+
int x = [[bounds objectForKey:@"X"] intValue];
|
|
521
|
+
int y = [[bounds objectForKey:@"Y"] intValue];
|
|
522
|
+
int width = [[bounds objectForKey:@"Width"] intValue];
|
|
523
|
+
int height = [[bounds objectForKey:@"Height"] intValue];
|
|
524
|
+
|
|
525
|
+
// Skip too small windows
|
|
526
|
+
if (width < 50 || height < 50) continue;
|
|
527
|
+
|
|
528
|
+
// Check if cursor is within window bounds
|
|
529
|
+
if (point.x >= x && point.x <= x + width &&
|
|
530
|
+
point.y >= y && point.y <= y + height) {
|
|
531
|
+
|
|
532
|
+
NSString *windowName = [windowInfo objectForKey:(NSString *)kCGWindowName];
|
|
533
|
+
|
|
534
|
+
// Create window info in our format
|
|
535
|
+
NSDictionary *window = @{
|
|
536
|
+
@"id": winID,
|
|
537
|
+
@"title": windowName ?: @"Untitled",
|
|
538
|
+
@"appName": owner,
|
|
539
|
+
@"x": @(x),
|
|
540
|
+
@"y": @(y),
|
|
541
|
+
@"width": @(width),
|
|
542
|
+
@"height": @(height)
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
CFRelease(windowList);
|
|
546
|
+
return window;
|
|
547
|
+
}
|
|
515
548
|
}
|
|
549
|
+
CFRelease(windowList);
|
|
516
550
|
}
|
|
517
551
|
|
|
518
552
|
return nil;
|
|
@@ -1274,26 +1308,54 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
|
|
|
1274
1308
|
NSLog(@" 📊 Details: ID=%@, Pos=(%d,%d), Size=%dx%d",
|
|
1275
1309
|
[g_selectedWindowInfo objectForKey:@"id"], x, y, width, height);
|
|
1276
1310
|
|
|
1277
|
-
// Get all screens
|
|
1311
|
+
// Get all screens and find which screen contains this window
|
|
1278
1312
|
NSArray *screens = [NSScreen screens];
|
|
1279
1313
|
NSScreen *windowScreen = nil;
|
|
1280
1314
|
NSScreen *mainScreen = [NSScreen mainScreen];
|
|
1281
1315
|
|
|
1316
|
+
// Calculate window center point for better screen detection
|
|
1317
|
+
CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
|
|
1318
|
+
|
|
1282
1319
|
for (NSScreen *screen in screens) {
|
|
1283
1320
|
NSRect screenFrame = [screen frame];
|
|
1284
1321
|
|
|
1285
|
-
//
|
|
1286
|
-
if (x >= screenFrame.origin.x &&
|
|
1287
|
-
x < screenFrame.origin.x + screenFrame.size.width &&
|
|
1288
|
-
y >= screenFrame.origin.y &&
|
|
1289
|
-
y < screenFrame.origin.y + screenFrame.size.height) {
|
|
1322
|
+
// Check if window center is within screen bounds
|
|
1323
|
+
if (windowCenter.x >= screenFrame.origin.x &&
|
|
1324
|
+
windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
|
|
1325
|
+
windowCenter.y >= screenFrame.origin.y &&
|
|
1326
|
+
windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
|
|
1290
1327
|
windowScreen = screen;
|
|
1328
|
+
NSLog(@" 🖥️ Window found on screen: (%.0f,%.0f) %.0fx%.0f",
|
|
1329
|
+
screenFrame.origin.x, screenFrame.origin.y,
|
|
1330
|
+
screenFrame.size.width, screenFrame.size.height);
|
|
1291
1331
|
break;
|
|
1292
1332
|
}
|
|
1293
1333
|
}
|
|
1294
1334
|
|
|
1335
|
+
// If no exact match, find screen with maximum overlap
|
|
1336
|
+
if (!windowScreen) {
|
|
1337
|
+
CGFloat maxOverlapArea = 0;
|
|
1338
|
+
NSRect windowRect = NSMakeRect(x, y, width, height);
|
|
1339
|
+
|
|
1340
|
+
for (NSScreen *screen in screens) {
|
|
1341
|
+
NSRect screenFrame = [screen frame];
|
|
1342
|
+
NSRect intersection = NSIntersectionRect(windowRect, screenFrame);
|
|
1343
|
+
CGFloat overlapArea = intersection.size.width * intersection.size.height;
|
|
1344
|
+
|
|
1345
|
+
if (overlapArea > maxOverlapArea) {
|
|
1346
|
+
maxOverlapArea = overlapArea;
|
|
1347
|
+
windowScreen = screen;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (windowScreen) {
|
|
1352
|
+
NSLog(@" 🖥️ Window assigned to screen with max overlap: %.0f pixels²", maxOverlapArea);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1295
1356
|
if (!windowScreen) {
|
|
1296
1357
|
windowScreen = mainScreen;
|
|
1358
|
+
NSLog(@" 🖥️ Window defaulted to main screen");
|
|
1297
1359
|
}
|
|
1298
1360
|
|
|
1299
1361
|
// Add screen information
|
|
@@ -1361,13 +1423,74 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
|
|
|
1361
1423
|
updateOverlay();
|
|
1362
1424
|
}
|
|
1363
1425
|
|
|
1426
|
+
// For Electron mode, also get real-time window under cursor
|
|
1427
|
+
const char* electronVersion = getenv("ELECTRON_VERSION");
|
|
1428
|
+
const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
|
|
1429
|
+
|
|
1364
1430
|
Napi::Object result = Napi::Object::New(env);
|
|
1365
1431
|
result.Set("isSelecting", Napi::Boolean::New(env, g_isWindowSelecting));
|
|
1366
1432
|
result.Set("hasSelectedWindow", Napi::Boolean::New(env, g_selectedWindowInfo != nil));
|
|
1367
1433
|
result.Set("windowCount", Napi::Number::New(env, g_allWindows ? [g_allWindows count] : 0));
|
|
1368
1434
|
result.Set("hasOverlay", Napi::Boolean::New(env, g_overlayWindow != nil));
|
|
1369
1435
|
|
|
1370
|
-
if (
|
|
1436
|
+
if (electronVersion || electronRunAs) {
|
|
1437
|
+
// In Electron mode, get real-time window under cursor
|
|
1438
|
+
@try {
|
|
1439
|
+
NSPoint mouseLocation = [NSEvent mouseLocation];
|
|
1440
|
+
NSScreen *mainScreen = [NSScreen mainScreen];
|
|
1441
|
+
CGFloat screenHeight = [mainScreen frame].size.height;
|
|
1442
|
+
CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
|
|
1443
|
+
|
|
1444
|
+
NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
|
|
1445
|
+
|
|
1446
|
+
if (windowUnderCursor) {
|
|
1447
|
+
Napi::Object currentWindow = Napi::Object::New(env);
|
|
1448
|
+
currentWindow.Set("id", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"id"] intValue]));
|
|
1449
|
+
currentWindow.Set("title", Napi::String::New(env, [[windowUnderCursor objectForKey:@"title"] UTF8String]));
|
|
1450
|
+
currentWindow.Set("appName", Napi::String::New(env, [[windowUnderCursor objectForKey:@"appName"] UTF8String]));
|
|
1451
|
+
currentWindow.Set("x", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"x"] intValue]));
|
|
1452
|
+
currentWindow.Set("y", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"y"] intValue]));
|
|
1453
|
+
currentWindow.Set("width", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"width"] intValue]));
|
|
1454
|
+
currentWindow.Set("height", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"height"] intValue]));
|
|
1455
|
+
|
|
1456
|
+
// Add screen detection for Electron
|
|
1457
|
+
int x = [[windowUnderCursor objectForKey:@"x"] intValue];
|
|
1458
|
+
int y = [[windowUnderCursor objectForKey:@"y"] intValue];
|
|
1459
|
+
int width = [[windowUnderCursor objectForKey:@"width"] intValue];
|
|
1460
|
+
int height = [[windowUnderCursor objectForKey:@"height"] intValue];
|
|
1461
|
+
|
|
1462
|
+
NSArray *screens = [NSScreen screens];
|
|
1463
|
+
NSScreen *windowScreen = nil;
|
|
1464
|
+
CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
|
|
1465
|
+
|
|
1466
|
+
for (NSScreen *screen in screens) {
|
|
1467
|
+
NSRect screenFrame = [screen frame];
|
|
1468
|
+
if (windowCenter.x >= screenFrame.origin.x &&
|
|
1469
|
+
windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
|
|
1470
|
+
windowCenter.y >= screenFrame.origin.y &&
|
|
1471
|
+
windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
|
|
1472
|
+
windowScreen = screen;
|
|
1473
|
+
break;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
if (windowScreen) {
|
|
1478
|
+
NSRect screenFrame = [windowScreen frame];
|
|
1479
|
+
currentWindow.Set("screenId", Napi::Number::New(env, [[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] ?
|
|
1480
|
+
[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue] : 0));
|
|
1481
|
+
currentWindow.Set("screenX", Napi::Number::New(env, (int)screenFrame.origin.x));
|
|
1482
|
+
currentWindow.Set("screenY", Napi::Number::New(env, (int)screenFrame.origin.y));
|
|
1483
|
+
currentWindow.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
|
|
1484
|
+
currentWindow.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
result.Set("currentWindow", currentWindow);
|
|
1488
|
+
}
|
|
1489
|
+
} @catch (NSException *exception) {
|
|
1490
|
+
// Ignore mouse tracking errors in Electron mode
|
|
1491
|
+
}
|
|
1492
|
+
} else if (g_currentWindowUnderCursor) {
|
|
1493
|
+
// Native mode
|
|
1371
1494
|
Napi::Object currentWindow = Napi::Object::New(env);
|
|
1372
1495
|
currentWindow.Set("id", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"id"] intValue]));
|
|
1373
1496
|
currentWindow.Set("title", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"title"] UTF8String]));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const ElectronWindowSelector = require('./electron-window-selector.js');
|
|
4
|
+
|
|
5
|
+
console.log('🧪 Testing Fixed Overlay Functionality');
|
|
6
|
+
console.log('=====================================');
|
|
7
|
+
|
|
8
|
+
async function testOverlayFunctionality() {
|
|
9
|
+
const selector = new ElectronWindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log('\n🔍 Environment Check:');
|
|
13
|
+
const status = selector.getStatus();
|
|
14
|
+
console.log(` - Electron Mode: ${status.isElectron}`);
|
|
15
|
+
|
|
16
|
+
console.log('\n🪟 Testing Window Detection...');
|
|
17
|
+
|
|
18
|
+
// Test real-time window detection
|
|
19
|
+
console.log('Move your mouse over different windows...');
|
|
20
|
+
console.log('Press Ctrl+C to stop\n');
|
|
21
|
+
|
|
22
|
+
let lastWindowId = null;
|
|
23
|
+
|
|
24
|
+
const checkInterval = setInterval(async () => {
|
|
25
|
+
try {
|
|
26
|
+
// Simulate what Electron app would do - poll for current window
|
|
27
|
+
const windowStatus = require('./build/Release/mac_recorder.node').getWindowSelectionStatus();
|
|
28
|
+
|
|
29
|
+
if (windowStatus && windowStatus.currentWindow) {
|
|
30
|
+
const window = windowStatus.currentWindow;
|
|
31
|
+
|
|
32
|
+
if (window.id !== lastWindowId) {
|
|
33
|
+
lastWindowId = window.id;
|
|
34
|
+
|
|
35
|
+
console.log(`🎯 Window Detected: ${window.appName} - "${window.title}"`);
|
|
36
|
+
console.log(` 📍 Position: (${window.x}, ${window.y})`);
|
|
37
|
+
console.log(` 📏 Size: ${window.width}x${window.height}`);
|
|
38
|
+
|
|
39
|
+
if (window.screenId !== undefined) {
|
|
40
|
+
console.log(` 🖥️ Screen: ${window.screenId} (${window.screenWidth}x${window.screenHeight})`);
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
} else if (lastWindowId !== null) {
|
|
45
|
+
lastWindowId = null;
|
|
46
|
+
console.log('🚪 No window under cursor\n');
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error during window detection:', error.message);
|
|
50
|
+
}
|
|
51
|
+
}, 100); // Check every 100ms for smooth tracking
|
|
52
|
+
|
|
53
|
+
// Handle Ctrl+C gracefully
|
|
54
|
+
process.on('SIGINT', () => {
|
|
55
|
+
console.log('\n\n🛑 Stopping test...');
|
|
56
|
+
clearInterval(checkInterval);
|
|
57
|
+
selector.cleanup().then(() => {
|
|
58
|
+
console.log('✅ Cleanup completed');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('❌ Test failed:', error.message);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Set Electron environment for testing
|
|
70
|
+
process.env.ELECTRON_VERSION = '25.0.0';
|
|
71
|
+
|
|
72
|
+
testOverlayFunctionality();
|