node-mac-recorder 1.2.10 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -1
- package/README.md +50 -6
- package/WINDOW_SELECTOR_README.md +396 -0
- package/binding.gyp +2 -1
- package/debug-audio.js +79 -0
- package/debug-window-selector.js +178 -0
- package/enhanced-window-selector.js +202 -0
- package/examples/integration-example.js +228 -0
- package/examples/window-selector-example.js +254 -0
- package/index.js +7 -2
- package/package.json +4 -2
- package/quick-test.js +50 -0
- package/simple-api-example.js +182 -0
- package/simple-test.js +38 -0
- package/src/mac_recorder.mm +76 -3
- package/src/screen_capture_kit.h +15 -0
- package/src/window_selector.mm +482 -0
- package/system-sound-test.js +46 -0
- package/test-system-audio.js +104 -0
- package/usage-examples.js +202 -0
- package/window-selector-test.js +160 -0
- package/window-selector.js +259 -0
- package/working-example.js +94 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const { EventEmitter } = require("events");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
// Native modülü yükle
|
|
5
|
+
let nativeBinding;
|
|
6
|
+
try {
|
|
7
|
+
nativeBinding = require("./build/Release/mac_recorder.node");
|
|
8
|
+
} catch (error) {
|
|
9
|
+
try {
|
|
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
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class WindowSelector extends EventEmitter {
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.isSelecting = false;
|
|
24
|
+
this.selectionTimer = null;
|
|
25
|
+
this.selectedWindow = null;
|
|
26
|
+
this.lastStatus = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Pencere seçim modunu başlatır
|
|
31
|
+
* İmleç hangi pencerenin üstüne gelirse o pencereyi highlight eder
|
|
32
|
+
* Select butonuna basılınca seçim tamamlanır
|
|
33
|
+
*/
|
|
34
|
+
async startSelection() {
|
|
35
|
+
if (this.isSelecting) {
|
|
36
|
+
throw new Error("Window selection is already in progress");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
try {
|
|
41
|
+
// Native window selection başlat
|
|
42
|
+
const success = nativeBinding.startWindowSelection();
|
|
43
|
+
|
|
44
|
+
if (success) {
|
|
45
|
+
this.isSelecting = true;
|
|
46
|
+
this.selectedWindow = null;
|
|
47
|
+
|
|
48
|
+
// Status polling timer başlat (higher frequency for overlay updates)
|
|
49
|
+
this.selectionTimer = setInterval(() => {
|
|
50
|
+
this.checkSelectionStatus();
|
|
51
|
+
}, 50); // 20 FPS status check for smooth overlay
|
|
52
|
+
|
|
53
|
+
this.emit("selectionStarted");
|
|
54
|
+
resolve(true);
|
|
55
|
+
} else {
|
|
56
|
+
reject(new Error("Failed to start window selection"));
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pencere seçim modunu durdurur
|
|
66
|
+
*/
|
|
67
|
+
async stopSelection() {
|
|
68
|
+
if (!this.isSelecting) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
try {
|
|
74
|
+
const success = nativeBinding.stopWindowSelection();
|
|
75
|
+
|
|
76
|
+
// Timer'ı durdur
|
|
77
|
+
if (this.selectionTimer) {
|
|
78
|
+
clearInterval(this.selectionTimer);
|
|
79
|
+
this.selectionTimer = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.isSelecting = false;
|
|
83
|
+
this.lastStatus = null;
|
|
84
|
+
|
|
85
|
+
this.emit("selectionStopped");
|
|
86
|
+
resolve(success);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Selection durumunu kontrol eder ve event yayar
|
|
95
|
+
*/
|
|
96
|
+
checkSelectionStatus() {
|
|
97
|
+
if (!this.isSelecting) return;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const status = nativeBinding.getWindowSelectionStatus();
|
|
101
|
+
|
|
102
|
+
// Seçim tamamlandı mı kontrol et
|
|
103
|
+
if (status.hasSelectedWindow && !this.selectedWindow) {
|
|
104
|
+
const windowInfo = nativeBinding.getSelectedWindowInfo();
|
|
105
|
+
if (windowInfo) {
|
|
106
|
+
this.selectedWindow = windowInfo;
|
|
107
|
+
this.isSelecting = false;
|
|
108
|
+
|
|
109
|
+
// Timer'ı durdur
|
|
110
|
+
if (this.selectionTimer) {
|
|
111
|
+
clearInterval(this.selectionTimer);
|
|
112
|
+
this.selectionTimer = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.emit("windowSelected", windowInfo);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Mevcut pencere değişti mi kontrol et
|
|
121
|
+
if (this.lastStatus) {
|
|
122
|
+
const lastWindow = this.lastStatus.currentWindow;
|
|
123
|
+
const currentWindow = status.currentWindow;
|
|
124
|
+
|
|
125
|
+
if (!lastWindow && currentWindow) {
|
|
126
|
+
// Yeni pencere üstüne gelindi
|
|
127
|
+
this.emit("windowEntered", currentWindow);
|
|
128
|
+
} else if (lastWindow && !currentWindow) {
|
|
129
|
+
// Pencere üstünden ayrıldı
|
|
130
|
+
this.emit("windowLeft", lastWindow);
|
|
131
|
+
} else if (lastWindow && currentWindow &&
|
|
132
|
+
(lastWindow.id !== currentWindow.id ||
|
|
133
|
+
lastWindow.title !== currentWindow.title ||
|
|
134
|
+
lastWindow.appName !== currentWindow.appName)) {
|
|
135
|
+
// Farklı bir pencereye geçildi
|
|
136
|
+
this.emit("windowLeft", lastWindow);
|
|
137
|
+
this.emit("windowEntered", currentWindow);
|
|
138
|
+
}
|
|
139
|
+
} else if (!this.lastStatus && status.currentWindow) {
|
|
140
|
+
// İlk pencere detection
|
|
141
|
+
this.emit("windowEntered", status.currentWindow);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.lastStatus = status;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
this.emit("error", error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Seçilen pencere bilgisini döndürür
|
|
152
|
+
*/
|
|
153
|
+
getSelectedWindow() {
|
|
154
|
+
return this.selectedWindow;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Seçim durumunu döndürür
|
|
159
|
+
*/
|
|
160
|
+
getStatus() {
|
|
161
|
+
try {
|
|
162
|
+
const nativeStatus = nativeBinding.getWindowSelectionStatus();
|
|
163
|
+
return {
|
|
164
|
+
isSelecting: this.isSelecting && nativeStatus.isSelecting,
|
|
165
|
+
hasSelectedWindow: !!this.selectedWindow,
|
|
166
|
+
selectedWindow: this.selectedWindow,
|
|
167
|
+
nativeStatus: nativeStatus
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
isSelecting: this.isSelecting,
|
|
172
|
+
hasSelectedWindow: !!this.selectedWindow,
|
|
173
|
+
selectedWindow: this.selectedWindow,
|
|
174
|
+
error: error.message
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Promise tabanlı pencere seçimi
|
|
181
|
+
* Kullanıcı bir pencere seçene kadar bekler
|
|
182
|
+
*/
|
|
183
|
+
async selectWindow() {
|
|
184
|
+
if (this.isSelecting) {
|
|
185
|
+
throw new Error("Selection already in progress");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return new Promise(async (resolve, reject) => {
|
|
189
|
+
try {
|
|
190
|
+
// Event listener'ları ayarla
|
|
191
|
+
const onWindowSelected = (windowInfo) => {
|
|
192
|
+
this.removeAllListeners("windowSelected");
|
|
193
|
+
this.removeAllListeners("error");
|
|
194
|
+
resolve(windowInfo);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const onError = (error) => {
|
|
198
|
+
this.removeAllListeners("windowSelected");
|
|
199
|
+
this.removeAllListeners("error");
|
|
200
|
+
reject(error);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
this.once("windowSelected", onWindowSelected);
|
|
204
|
+
this.once("error", onError);
|
|
205
|
+
|
|
206
|
+
// Seçimi başlat
|
|
207
|
+
await this.startSelection();
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.removeAllListeners("windowSelected");
|
|
211
|
+
this.removeAllListeners("error");
|
|
212
|
+
reject(error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Cleanup - tüm kaynakları temizle
|
|
219
|
+
*/
|
|
220
|
+
async cleanup() {
|
|
221
|
+
if (this.isSelecting) {
|
|
222
|
+
await this.stopSelection();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Timer'ı temizle
|
|
226
|
+
if (this.selectionTimer) {
|
|
227
|
+
clearInterval(this.selectionTimer);
|
|
228
|
+
this.selectionTimer = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Event listener'ları temizle
|
|
232
|
+
this.removeAllListeners();
|
|
233
|
+
|
|
234
|
+
// State'i sıfırla
|
|
235
|
+
this.selectedWindow = null;
|
|
236
|
+
this.lastStatus = null;
|
|
237
|
+
this.isSelecting = false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* macOS'ta pencere seçim izinlerini kontrol eder
|
|
242
|
+
*/
|
|
243
|
+
async checkPermissions() {
|
|
244
|
+
try {
|
|
245
|
+
// Mevcut MacRecorder'dan permission check'i kullan
|
|
246
|
+
const MacRecorder = require("./index.js");
|
|
247
|
+
const recorder = new MacRecorder();
|
|
248
|
+
return await recorder.checkPermissions();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return {
|
|
251
|
+
screenRecording: false,
|
|
252
|
+
accessibility: false,
|
|
253
|
+
error: error.message
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = WindowSelector;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const WindowSelector = require('./window-selector');
|
|
4
|
+
|
|
5
|
+
async function workingExample() {
|
|
6
|
+
console.log('🎯 Window Selector - Working Example');
|
|
7
|
+
console.log('====================================\n');
|
|
8
|
+
|
|
9
|
+
const selector = new WindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log('Starting window selection...');
|
|
13
|
+
console.log('Move cursor over different windows to see detection');
|
|
14
|
+
console.log('The system will detect which window is under cursor');
|
|
15
|
+
console.log('Press Ctrl+C to stop\n');
|
|
16
|
+
|
|
17
|
+
let currentWindow = null;
|
|
18
|
+
|
|
19
|
+
selector.on('windowEntered', (window) => {
|
|
20
|
+
currentWindow = window;
|
|
21
|
+
console.log(`\n🏠 ENTERED WINDOW:`);
|
|
22
|
+
console.log(` App: ${window.appName}`);
|
|
23
|
+
console.log(` Title: "${window.title}"`);
|
|
24
|
+
console.log(` Position: (${window.x}, ${window.y})`);
|
|
25
|
+
console.log(` Size: ${window.width} x ${window.height}`);
|
|
26
|
+
console.log(` 💡 This window is now highlighted (overlay may not be visible due to macOS security)`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
selector.on('windowLeft', (window) => {
|
|
30
|
+
console.log(`\n🚪 LEFT WINDOW: ${window.appName} - "${window.title}"`);
|
|
31
|
+
currentWindow = null;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
selector.on('windowSelected', (selectedWindow) => {
|
|
35
|
+
console.log('\n🎉 WINDOW SELECTED!');
|
|
36
|
+
console.log('==================');
|
|
37
|
+
console.log(`App: ${selectedWindow.appName}`);
|
|
38
|
+
console.log(`Title: "${selectedWindow.title}"`);
|
|
39
|
+
console.log(`Position: (${selectedWindow.x}, ${selectedWindow.y})`);
|
|
40
|
+
console.log(`Size: ${selectedWindow.width} x ${selectedWindow.height}`);
|
|
41
|
+
console.log(`Screen: ${selectedWindow.screenId}`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Manual selection trigger
|
|
46
|
+
let readline = require('readline');
|
|
47
|
+
const rl = readline.createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log('💡 Pro tip: Press ENTER to select the current window under cursor');
|
|
53
|
+
rl.on('line', () => {
|
|
54
|
+
if (currentWindow) {
|
|
55
|
+
console.log('\n✅ Manually selecting current window...');
|
|
56
|
+
selector.emit('windowSelected', currentWindow);
|
|
57
|
+
} else {
|
|
58
|
+
console.log('\n⚠️ No window under cursor. Move cursor over a window first.');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await selector.startSelection();
|
|
63
|
+
|
|
64
|
+
// Status monitoring
|
|
65
|
+
let statusCount = 0;
|
|
66
|
+
const statusInterval = setInterval(() => {
|
|
67
|
+
const status = selector.getStatus();
|
|
68
|
+
statusCount++;
|
|
69
|
+
|
|
70
|
+
if (statusCount % 20 === 0) { // Every 10 seconds
|
|
71
|
+
console.log(`\n📊 Status (${statusCount/2}s): Windows detected: ${status.nativeStatus?.windowCount || 0}`);
|
|
72
|
+
if (status.nativeStatus?.currentWindow) {
|
|
73
|
+
console.log(` Current: ${status.nativeStatus.currentWindow.appName}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}, 500);
|
|
77
|
+
|
|
78
|
+
process.on('SIGINT', async () => {
|
|
79
|
+
clearInterval(statusInterval);
|
|
80
|
+
rl.close();
|
|
81
|
+
console.log('\n🛑 Stopping window selector...');
|
|
82
|
+
await selector.cleanup();
|
|
83
|
+
console.log('✅ Cleanup completed');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('\n❌ Error:', error.message);
|
|
89
|
+
console.error(error.stack);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
workingExample();
|