node-mac-recorder 1.3.0 → 1.5.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/WINDOW_SELECTOR_README.md +66 -0
- package/auto-front-demo.js +71 -0
- package/bring-to-front-test.js +159 -0
- package/debug-test.js +95 -0
- package/default-auto-front-test.js +86 -0
- package/package.json +1 -1
- package/src/window_selector.mm +181 -9
- package/window-selector.js +35 -0
|
@@ -12,6 +12,8 @@ Bu modül, macOS'ta sistem imleci ile pencere seçimi yapabilmenizi sağlayan g
|
|
|
12
12
|
- **Multi-display Support**: Çoklu ekran kurulumlarında çalışır
|
|
13
13
|
- **Detailed Window Info**: Pencere pozisyonu, boyutu ve hangi ekranda olduğunu döndürür
|
|
14
14
|
- **Event-driven API**: Pencere hover, seçim ve hata durumları için event'ler
|
|
15
|
+
- **Window Focus Control**: Detect edilen pencereyi otomatik olarak en öne getirir
|
|
16
|
+
- **Auto Bring-to-Front**: Cursor hangi pencereye gelirse otomatik focus yapar
|
|
15
17
|
- **Permission Management**: macOS izin kontrolü ve yönetimi
|
|
16
18
|
|
|
17
19
|
## 🚀 Kurulum
|
|
@@ -150,6 +152,29 @@ macOS izinlerini kontrol eder.
|
|
|
150
152
|
|
|
151
153
|
**Returns:** `Promise<PermissionStatus>`
|
|
152
154
|
|
|
155
|
+
##### `async bringWindowToFront(windowId)`
|
|
156
|
+
Belirtilen pencereyi en öne getirir (focus yapar).
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
- `windowId` (number) - Window ID
|
|
160
|
+
|
|
161
|
+
**Returns:** `Promise<boolean>` - Başarı/başarısızlık
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const success = await selector.bringWindowToFront(windowInfo.id);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
##### `setBringToFrontEnabled(enabled)`
|
|
168
|
+
Otomatik pencere en öne getirme özelliğini aktif/pasif yapar.
|
|
169
|
+
|
|
170
|
+
**Parameters:**
|
|
171
|
+
- `enabled` (boolean) - Enable/disable
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
selector.setBringToFrontEnabled(true); // Auto mode ON
|
|
175
|
+
selector.setBringToFrontEnabled(false); // Auto mode OFF
|
|
176
|
+
```
|
|
177
|
+
|
|
153
178
|
##### `async cleanup()`
|
|
154
179
|
Tüm kaynakları temizler ve seçimi durdurur.
|
|
155
180
|
|
|
@@ -309,6 +334,47 @@ if (!permissions.screenRecording) {
|
|
|
309
334
|
|
|
310
335
|
## 🌟 Gelişmiş Örnekler
|
|
311
336
|
|
|
337
|
+
### Auto Bring-to-Front (DEFAULT - Otomatik Focus)
|
|
338
|
+
```javascript
|
|
339
|
+
const WindowSelector = require('./window-selector');
|
|
340
|
+
|
|
341
|
+
async function autoBringToFront() {
|
|
342
|
+
const selector = new WindowSelector();
|
|
343
|
+
|
|
344
|
+
// Auto bring-to-front varsayılan olarak AÇIK
|
|
345
|
+
// (Kapatmak için: selector.setBringToFrontEnabled(false))
|
|
346
|
+
|
|
347
|
+
selector.on('windowEntered', (window) => {
|
|
348
|
+
console.log(`🔝 Auto-focused: ${window.appName} - "${window.title}"`);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
await selector.startSelection();
|
|
352
|
+
console.log('🖱️ Move cursor over windows - they will come to front automatically!');
|
|
353
|
+
console.log('💡 Only the specific window focuses, not all windows of the app');
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Manuel Window Focus
|
|
358
|
+
```javascript
|
|
359
|
+
const WindowSelector = require('./window-selector');
|
|
360
|
+
|
|
361
|
+
async function manualFocus() {
|
|
362
|
+
const selector = new WindowSelector();
|
|
363
|
+
|
|
364
|
+
selector.on('windowEntered', async (window) => {
|
|
365
|
+
console.log(`Found: ${window.appName} - "${window.title}"`);
|
|
366
|
+
|
|
367
|
+
// Manuel olarak pencereyi en öne getir
|
|
368
|
+
const success = await selector.bringWindowToFront(window.id);
|
|
369
|
+
if (success) {
|
|
370
|
+
console.log('✅ Window brought to front!');
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await selector.startSelection();
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
312
378
|
### Otomatik Pencere Kaydı
|
|
313
379
|
```javascript
|
|
314
380
|
const WindowSelector = require('./window-selector');
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const WindowSelector = require('./window-selector');
|
|
4
|
+
|
|
5
|
+
async function autoBringToFrontDemo() {
|
|
6
|
+
console.log('🤖 Auto Bring-To-Front Demo');
|
|
7
|
+
console.log('============================\n');
|
|
8
|
+
|
|
9
|
+
const selector = new WindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log('🔄 Enabling auto bring-to-front feature...');
|
|
13
|
+
selector.setBringToFrontEnabled(true);
|
|
14
|
+
|
|
15
|
+
console.log('✅ Auto mode enabled!');
|
|
16
|
+
console.log('🖱️ Now move your cursor over different windows');
|
|
17
|
+
console.log('🔝 Each window should automatically come to front\n');
|
|
18
|
+
|
|
19
|
+
let windowCount = 0;
|
|
20
|
+
let lastWindowId = null;
|
|
21
|
+
|
|
22
|
+
selector.on('windowEntered', (window) => {
|
|
23
|
+
if (window.id !== lastWindowId) {
|
|
24
|
+
windowCount++;
|
|
25
|
+
console.log(`[${windowCount}] 🎯 AUTO-FRONT: ${window.appName} - "${window.title}"`);
|
|
26
|
+
console.log(` 📍 Position: (${window.x}, ${window.y})`);
|
|
27
|
+
console.log(` 📏 Size: ${window.width} × ${window.height}`);
|
|
28
|
+
console.log(` 🔝 Window should come to front automatically!\n`);
|
|
29
|
+
lastWindowId = window.id;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
selector.on('windowLeft', (window) => {
|
|
34
|
+
console.log(`🚪 Left: ${window.appName} - "${window.title}"\n`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await selector.startSelection();
|
|
38
|
+
|
|
39
|
+
console.log('Demo started! Move cursor over different app windows to see them come to front.');
|
|
40
|
+
console.log('Press Ctrl+C to stop\n');
|
|
41
|
+
|
|
42
|
+
// Auto-stop after 60 seconds
|
|
43
|
+
setTimeout(async () => {
|
|
44
|
+
console.log('\n⏰ Demo completed!');
|
|
45
|
+
console.log(`📊 Total windows auto-focused: ${windowCount}`);
|
|
46
|
+
selector.setBringToFrontEnabled(false);
|
|
47
|
+
await selector.cleanup();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}, 60000);
|
|
50
|
+
|
|
51
|
+
// Manual stop
|
|
52
|
+
process.on('SIGINT', async () => {
|
|
53
|
+
console.log('\n\n🛑 Stopping demo...');
|
|
54
|
+
console.log(`📊 Total windows auto-focused: ${windowCount}`);
|
|
55
|
+
selector.setBringToFrontEnabled(false);
|
|
56
|
+
await selector.cleanup();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Prevent exit
|
|
61
|
+
setInterval(() => {}, 1000);
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('❌ Error:', error.message);
|
|
65
|
+
await selector.cleanup();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (require.main === module) {
|
|
70
|
+
autoBringToFrontDemo();
|
|
71
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const WindowSelector = require('./window-selector');
|
|
4
|
+
|
|
5
|
+
async function testBringToFront() {
|
|
6
|
+
console.log('🔝 Bring To Front Test');
|
|
7
|
+
console.log('======================\n');
|
|
8
|
+
|
|
9
|
+
const selector = new WindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Test 1: Manual bring to front
|
|
13
|
+
console.log('📋 Test 1: Manual window bring-to-front');
|
|
14
|
+
console.log('Move cursor over a window and press SPACE to bring it to front\n');
|
|
15
|
+
|
|
16
|
+
let currentWindow = null;
|
|
17
|
+
|
|
18
|
+
selector.on('windowEntered', (window) => {
|
|
19
|
+
currentWindow = window;
|
|
20
|
+
console.log(`\n🏠 Window: ${window.appName} - "${window.title}" (ID: ${window.id})`);
|
|
21
|
+
console.log(' 📍 Position:', `(${window.x}, ${window.y})`);
|
|
22
|
+
console.log(' 📏 Size:', `${window.width} × ${window.height}`);
|
|
23
|
+
console.log(' 💡 Press SPACE to bring this window to front');
|
|
24
|
+
console.log(' 💡 Press A to enable AUTO bring-to-front');
|
|
25
|
+
console.log(' 💡 Press D to disable AUTO bring-to-front');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
selector.on('windowLeft', (window) => {
|
|
29
|
+
console.log(`🚪 Left: ${window.appName} - "${window.title}"`);
|
|
30
|
+
currentWindow = null;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Keyboard controls
|
|
34
|
+
const readline = require('readline');
|
|
35
|
+
readline.emitKeypressEvents(process.stdin);
|
|
36
|
+
if (process.stdin.isTTY) {
|
|
37
|
+
process.stdin.setRawMode(true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.stdin.on('keypress', async (str, key) => {
|
|
41
|
+
if (key.name === 'space' && currentWindow) {
|
|
42
|
+
console.log(`\n🔝 Bringing window to front: ${currentWindow.appName} - "${currentWindow.title}"`);
|
|
43
|
+
try {
|
|
44
|
+
const success = await selector.bringWindowToFront(currentWindow.id);
|
|
45
|
+
if (success) {
|
|
46
|
+
console.log(' ✅ Window brought to front successfully!');
|
|
47
|
+
} else {
|
|
48
|
+
console.log(' ❌ Failed to bring window to front');
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log(' ❌ Error:', error.message);
|
|
52
|
+
}
|
|
53
|
+
} else if (key.name === 'a') {
|
|
54
|
+
console.log('\n🔄 Enabling AUTO bring-to-front mode...');
|
|
55
|
+
selector.setBringToFrontEnabled(true);
|
|
56
|
+
console.log(' ✅ Auto mode ON - Windows will come to front automatically');
|
|
57
|
+
} else if (key.name === 'd') {
|
|
58
|
+
console.log('\n🔄 Disabling AUTO bring-to-front mode...');
|
|
59
|
+
selector.setBringToFrontEnabled(false);
|
|
60
|
+
console.log(' ✅ Auto mode OFF - Manual control only');
|
|
61
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('🚀 Starting window selection...\n');
|
|
67
|
+
console.log('📋 Controls:');
|
|
68
|
+
console.log(' SPACE - Bring current window to front');
|
|
69
|
+
console.log(' A - Enable AUTO bring-to-front');
|
|
70
|
+
console.log(' D - Disable AUTO bring-to-front');
|
|
71
|
+
console.log(' Ctrl+C - Exit\n');
|
|
72
|
+
|
|
73
|
+
await selector.startSelection();
|
|
74
|
+
|
|
75
|
+
// Keep running
|
|
76
|
+
process.on('SIGINT', async () => {
|
|
77
|
+
console.log('\n🛑 Stopping...');
|
|
78
|
+
await selector.cleanup();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Prevent exit
|
|
83
|
+
setInterval(() => {}, 1000);
|
|
84
|
+
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('❌ Error:', error.message);
|
|
87
|
+
console.error(error.stack);
|
|
88
|
+
} finally {
|
|
89
|
+
// Cleanup
|
|
90
|
+
await selector.cleanup();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function testAutoBringToFront() {
|
|
95
|
+
console.log('🤖 Auto Bring To Front Test');
|
|
96
|
+
console.log('============================\n');
|
|
97
|
+
|
|
98
|
+
const selector = new WindowSelector();
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Enable auto bring-to-front
|
|
102
|
+
console.log('🔄 Enabling auto bring-to-front...');
|
|
103
|
+
selector.setBringToFrontEnabled(true);
|
|
104
|
+
|
|
105
|
+
let windowCount = 0;
|
|
106
|
+
|
|
107
|
+
selector.on('windowEntered', (window) => {
|
|
108
|
+
windowCount++;
|
|
109
|
+
console.log(`\n[${windowCount}] 🔝 AUTO FRONT: ${window.appName} - "${window.title}"`);
|
|
110
|
+
console.log(` 📍 Position: (${window.x}, ${window.y})`);
|
|
111
|
+
console.log(` 📏 Size: ${window.width} × ${window.height}`);
|
|
112
|
+
console.log(' 🚀 Window should automatically come to front!');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log('✅ Auto bring-to-front enabled');
|
|
116
|
+
console.log('🖱️ Move cursor over different windows');
|
|
117
|
+
console.log('🔝 Each window should automatically come to front');
|
|
118
|
+
console.log('⏱️ Test will run for 30 seconds\n');
|
|
119
|
+
|
|
120
|
+
await selector.startSelection();
|
|
121
|
+
|
|
122
|
+
// Auto-stop after 30 seconds
|
|
123
|
+
setTimeout(async () => {
|
|
124
|
+
console.log('\n⏰ Test completed!');
|
|
125
|
+
console.log(`📊 Total windows auto-focused: ${windowCount}`);
|
|
126
|
+
selector.setBringToFrontEnabled(false);
|
|
127
|
+
await selector.cleanup();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}, 30000);
|
|
130
|
+
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('❌ Error:', error.message);
|
|
133
|
+
await selector.cleanup();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Main function
|
|
138
|
+
async function main() {
|
|
139
|
+
const args = process.argv.slice(2);
|
|
140
|
+
|
|
141
|
+
if (args.includes('--auto')) {
|
|
142
|
+
await testAutoBringToFront();
|
|
143
|
+
} else if (args.includes('--help')) {
|
|
144
|
+
console.log('Bring To Front Tests:');
|
|
145
|
+
console.log('====================');
|
|
146
|
+
console.log('node bring-to-front-test.js [option]');
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log('Options:');
|
|
149
|
+
console.log(' --manual Manual bring-to-front test (default)');
|
|
150
|
+
console.log(' --auto Auto bring-to-front test');
|
|
151
|
+
console.log(' --help Show this help');
|
|
152
|
+
} else {
|
|
153
|
+
await testBringToFront();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (require.main === module) {
|
|
158
|
+
main().catch(console.error);
|
|
159
|
+
}
|
package/debug-test.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const WindowSelector = require('./window-selector');
|
|
4
|
+
|
|
5
|
+
async function debugTest() {
|
|
6
|
+
console.log('🔍 Debug Window Selector Test');
|
|
7
|
+
console.log('==============================\n');
|
|
8
|
+
|
|
9
|
+
const selector = new WindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// İzinleri kontrol et
|
|
13
|
+
console.log('📋 Checking permissions...');
|
|
14
|
+
const permissions = await selector.checkPermissions();
|
|
15
|
+
console.log('Permissions:', JSON.stringify(permissions, null, 2));
|
|
16
|
+
|
|
17
|
+
if (!permissions.screenRecording || !permissions.accessibility) {
|
|
18
|
+
console.log('\n❌ MISSING PERMISSIONS!');
|
|
19
|
+
console.log('Please enable in System Preferences > Security & Privacy:');
|
|
20
|
+
console.log(' ✓ Screen Recording - Add Terminal/your IDE');
|
|
21
|
+
console.log(' ✓ Accessibility - Add Terminal/your IDE');
|
|
22
|
+
console.log('\nAfter enabling permissions, restart this test.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('✅ Permissions OK\n');
|
|
27
|
+
|
|
28
|
+
// Debug mode ile başlat
|
|
29
|
+
console.log('🚀 Starting selection with debug info...');
|
|
30
|
+
await selector.startSelection();
|
|
31
|
+
|
|
32
|
+
let windowCount = 0;
|
|
33
|
+
|
|
34
|
+
selector.on('windowEntered', (window) => {
|
|
35
|
+
windowCount++;
|
|
36
|
+
console.log(`\n[${windowCount}] 🎯 WINDOW DETECTED:`);
|
|
37
|
+
console.log(` App: ${window.appName}`);
|
|
38
|
+
console.log(` Title: "${window.title}"`);
|
|
39
|
+
console.log(` ID: ${window.id}`);
|
|
40
|
+
console.log(` Position: (${window.x}, ${window.y})`);
|
|
41
|
+
console.log(` Size: ${window.width} × ${window.height}`);
|
|
42
|
+
console.log(` 🔝 Should auto-focus now...`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
selector.on('windowLeft', (window) => {
|
|
46
|
+
console.log(`\n🚪 LEFT WINDOW: ${window.appName} - "${window.title}"`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
selector.on('error', (error) => {
|
|
50
|
+
console.error('\n❌ ERROR:', error.message);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log('📋 Test Instructions:');
|
|
54
|
+
console.log(' 1. Move cursor over different application windows');
|
|
55
|
+
console.log(' 2. You should see:');
|
|
56
|
+
console.log(' - Blue overlay rectangle around windows');
|
|
57
|
+
console.log(' - "Select Window" button in center');
|
|
58
|
+
console.log(' - Windows automatically coming to front');
|
|
59
|
+
console.log(' 3. If overlay not visible, check permissions');
|
|
60
|
+
console.log(' 4. Press Ctrl+C to exit\n');
|
|
61
|
+
console.log('🖱️ START MOVING CURSOR NOW...\n');
|
|
62
|
+
|
|
63
|
+
// Status monitoring
|
|
64
|
+
let statusCount = 0;
|
|
65
|
+
setInterval(() => {
|
|
66
|
+
statusCount++;
|
|
67
|
+
const status = selector.getStatus();
|
|
68
|
+
|
|
69
|
+
if (statusCount % 50 === 0) { // Every 5 seconds
|
|
70
|
+
console.log(`⏱️ Status Check #${statusCount/50}:`);
|
|
71
|
+
console.log(` - Selecting: ${status.isSelecting}`);
|
|
72
|
+
console.log(` - Windows found: ${status.nativeStatus?.windowCount || 0}`);
|
|
73
|
+
console.log(` - Overlay active: ${status.nativeStatus?.hasOverlay || false}`);
|
|
74
|
+
if (status.nativeStatus?.currentWindow) {
|
|
75
|
+
console.log(` - Current: ${status.nativeStatus.currentWindow.appName}`);
|
|
76
|
+
}
|
|
77
|
+
console.log('');
|
|
78
|
+
}
|
|
79
|
+
}, 100);
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('❌ Fatal Error:', error.message);
|
|
83
|
+
console.error(error.stack);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle Ctrl+C gracefully
|
|
88
|
+
process.on('SIGINT', async () => {
|
|
89
|
+
console.log('\n\n🛑 Stopping debug test...');
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (require.main === module) {
|
|
94
|
+
debugTest();
|
|
95
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const WindowSelector = require('./window-selector');
|
|
4
|
+
|
|
5
|
+
async function testDefaultAutoBringToFront() {
|
|
6
|
+
console.log('🔝 Default Auto Bring-To-Front Test');
|
|
7
|
+
console.log('====================================\n');
|
|
8
|
+
|
|
9
|
+
const selector = new WindowSelector();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log('🚀 Starting window selector with DEFAULT auto bring-to-front...');
|
|
13
|
+
console.log('(Auto bring-to-front is now enabled by default)\n');
|
|
14
|
+
|
|
15
|
+
console.log('📋 Instructions:');
|
|
16
|
+
console.log(' • Move cursor over different windows');
|
|
17
|
+
console.log(' • Each window should automatically come to front');
|
|
18
|
+
console.log(' • Only the specific window should focus (not whole app)');
|
|
19
|
+
console.log(' • Press D to disable auto mode');
|
|
20
|
+
console.log(' • Press E to re-enable auto mode');
|
|
21
|
+
console.log(' • Press Ctrl+C to exit\n');
|
|
22
|
+
|
|
23
|
+
let windowCount = 0;
|
|
24
|
+
let lastWindowId = null;
|
|
25
|
+
|
|
26
|
+
selector.on('windowEntered', (window) => {
|
|
27
|
+
if (window.id !== lastWindowId) {
|
|
28
|
+
windowCount++;
|
|
29
|
+
console.log(`[${windowCount}] 🎯 WINDOW: ${window.appName} - "${window.title}"`);
|
|
30
|
+
console.log(` 📍 Position: (${window.x}, ${window.y})`);
|
|
31
|
+
console.log(` 📏 Size: ${window.width} × ${window.height}`);
|
|
32
|
+
console.log(` 🔝 Should auto-focus THIS specific window only!`);
|
|
33
|
+
lastWindowId = window.id;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
selector.on('windowLeft', (window) => {
|
|
38
|
+
console.log(`🚪 Left: ${window.appName} - "${window.title}"\n`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Keyboard controls
|
|
42
|
+
const readline = require('readline');
|
|
43
|
+
readline.emitKeypressEvents(process.stdin);
|
|
44
|
+
if (process.stdin.isTTY) {
|
|
45
|
+
process.stdin.setRawMode(true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
process.stdin.on('keypress', async (str, key) => {
|
|
49
|
+
if (key.name === 'd') {
|
|
50
|
+
console.log('\n🔄 Disabling auto bring-to-front...');
|
|
51
|
+
selector.setBringToFrontEnabled(false);
|
|
52
|
+
console.log(' ✅ Auto mode OFF - Windows will not auto-focus');
|
|
53
|
+
} else if (key.name === 'e') {
|
|
54
|
+
console.log('\n🔄 Enabling auto bring-to-front...');
|
|
55
|
+
selector.setBringToFrontEnabled(true);
|
|
56
|
+
console.log(' ✅ Auto mode ON - Windows will auto-focus again');
|
|
57
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
58
|
+
console.log('\n\n🛑 Stopping...');
|
|
59
|
+
console.log(`📊 Total windows encountered: ${windowCount}`);
|
|
60
|
+
await selector.cleanup();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await selector.startSelection();
|
|
66
|
+
|
|
67
|
+
// Status update every 10 seconds
|
|
68
|
+
setInterval(() => {
|
|
69
|
+
console.log(`\n⏱️ Status: ${windowCount} windows encountered so far`);
|
|
70
|
+
console.log(' (Continue moving cursor over windows to test auto-focus)');
|
|
71
|
+
}, 10000);
|
|
72
|
+
|
|
73
|
+
// Keep running
|
|
74
|
+
setInterval(() => {}, 1000);
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('❌ Error:', error.message);
|
|
78
|
+
console.error(error.stack);
|
|
79
|
+
} finally {
|
|
80
|
+
await selector.cleanup();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (require.main === module) {
|
|
85
|
+
testDefaultAutoBringToFront();
|
|
86
|
+
}
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -15,12 +15,14 @@ static NSTimer *g_trackingTimer = nil;
|
|
|
15
15
|
static NSDictionary *g_selectedWindowInfo = nil;
|
|
16
16
|
static NSMutableArray *g_allWindows = nil;
|
|
17
17
|
static NSDictionary *g_currentWindowUnderCursor = nil;
|
|
18
|
+
static bool g_bringToFrontEnabled = true; // Default enabled
|
|
18
19
|
|
|
19
20
|
// Forward declarations
|
|
20
21
|
void cleanupWindowSelector();
|
|
21
22
|
void updateOverlay();
|
|
22
23
|
NSDictionary* getWindowUnderCursor(CGPoint point);
|
|
23
24
|
NSArray* getAllSelectableWindows();
|
|
25
|
+
bool bringWindowToFront(int windowId);
|
|
24
26
|
|
|
25
27
|
// Custom overlay view class
|
|
26
28
|
@interface WindowSelectorOverlayView : NSView
|
|
@@ -33,8 +35,8 @@ NSArray* getAllSelectableWindows();
|
|
|
33
35
|
self = [super initWithFrame:frameRect];
|
|
34
36
|
if (self) {
|
|
35
37
|
self.wantsLayer = YES;
|
|
36
|
-
self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.
|
|
37
|
-
self.layer.borderColor = [[NSColor colorWithRed:
|
|
38
|
+
self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.6] CGColor];
|
|
39
|
+
self.layer.borderColor = [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor];
|
|
38
40
|
self.layer.borderWidth = 5.0;
|
|
39
41
|
self.layer.cornerRadius = 8.0;
|
|
40
42
|
}
|
|
@@ -47,11 +49,11 @@ NSArray* getAllSelectableWindows();
|
|
|
47
49
|
if (!self.windowInfo) return;
|
|
48
50
|
|
|
49
51
|
// Background with transparency
|
|
50
|
-
[[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.
|
|
52
|
+
[[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.6] setFill];
|
|
51
53
|
NSRectFill(dirtyRect);
|
|
52
54
|
|
|
53
55
|
// Border
|
|
54
|
-
[[NSColor colorWithRed:
|
|
56
|
+
[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] setStroke];
|
|
55
57
|
NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:8 yRadius:8];
|
|
56
58
|
[border setLineWidth:3.0];
|
|
57
59
|
[border stroke];
|
|
@@ -65,14 +67,14 @@ NSArray* getAllSelectableWindows();
|
|
|
65
67
|
[style setAlignment:NSTextAlignmentCenter];
|
|
66
68
|
|
|
67
69
|
NSDictionary *attributes = @{
|
|
68
|
-
NSFontAttributeName: [NSFont systemFontOfSize:
|
|
70
|
+
NSFontAttributeName: [NSFont systemFontOfSize:21 weight:NSFontWeightMedium],
|
|
69
71
|
NSForegroundColorAttributeName: [NSColor whiteColor],
|
|
70
72
|
NSParagraphStyleAttributeName: style,
|
|
71
73
|
NSStrokeColorAttributeName: [NSColor blackColor],
|
|
72
74
|
NSStrokeWidthAttributeName: @(-2.0)
|
|
73
75
|
};
|
|
74
76
|
|
|
75
|
-
NSRect textRect = NSMakeRect(10, self.bounds.size.height -
|
|
77
|
+
NSRect textRect = NSMakeRect(10, self.bounds.size.height - 90, self.bounds.size.width - 20, 80);
|
|
76
78
|
[infoText drawInRect:textRect withAttributes:attributes];
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -99,6 +101,112 @@ NSArray* getAllSelectableWindows();
|
|
|
99
101
|
|
|
100
102
|
static WindowSelectorDelegate *g_delegate = nil;
|
|
101
103
|
|
|
104
|
+
// Bring window to front using Accessibility API
|
|
105
|
+
bool bringWindowToFront(int windowId) {
|
|
106
|
+
@autoreleasepool {
|
|
107
|
+
@try {
|
|
108
|
+
// Method 1: Using Accessibility API (most reliable)
|
|
109
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
110
|
+
if (!systemWide) return false;
|
|
111
|
+
|
|
112
|
+
CFArrayRef windowList = NULL;
|
|
113
|
+
AXError error = AXUIElementCopyAttributeValue(systemWide, kAXWindowsAttribute, (CFTypeRef*)&windowList);
|
|
114
|
+
|
|
115
|
+
if (error == kAXErrorSuccess && windowList) {
|
|
116
|
+
CFIndex windowCount = CFArrayGetCount(windowList);
|
|
117
|
+
|
|
118
|
+
for (CFIndex i = 0; i < windowCount; i++) {
|
|
119
|
+
AXUIElementRef windowElement = (AXUIElementRef)CFArrayGetValueAtIndex(windowList, i);
|
|
120
|
+
|
|
121
|
+
// Get window ID by comparing with CGWindowList
|
|
122
|
+
// Since _AXUIElementGetWindow is not available, we'll use app PID approach
|
|
123
|
+
pid_t windowPid;
|
|
124
|
+
error = AXUIElementGetPid(windowElement, &windowPid);
|
|
125
|
+
|
|
126
|
+
if (error == kAXErrorSuccess) {
|
|
127
|
+
// Get window info for this PID from CGWindowList
|
|
128
|
+
CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
|
|
129
|
+
if (cgWindowList) {
|
|
130
|
+
NSArray *windowArray = (__bridge NSArray *)cgWindowList;
|
|
131
|
+
|
|
132
|
+
for (NSDictionary *windowInfo in windowArray) {
|
|
133
|
+
NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
134
|
+
NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
|
|
135
|
+
|
|
136
|
+
if ([cgWindowId intValue] == windowId && [processId intValue] == windowPid) {
|
|
137
|
+
// Found the window, bring it to front
|
|
138
|
+
NSLog(@"🔝 BRINGING TO FRONT: Window ID %d (PID: %d)", windowId, windowPid);
|
|
139
|
+
|
|
140
|
+
// Method 1: Raise specific window (not the whole app)
|
|
141
|
+
error = AXUIElementPerformAction(windowElement, kAXRaiseAction);
|
|
142
|
+
if (error == kAXErrorSuccess) {
|
|
143
|
+
NSLog(@" ✅ Specific window raised successfully");
|
|
144
|
+
} else {
|
|
145
|
+
NSLog(@" ⚠️ Raise action failed: %d", error);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Method 2: Focus specific window (not main window)
|
|
149
|
+
error = AXUIElementSetAttributeValue(windowElement, kAXFocusedAttribute, kCFBooleanTrue);
|
|
150
|
+
if (error == kAXErrorSuccess) {
|
|
151
|
+
NSLog(@" ✅ Specific window focused");
|
|
152
|
+
} else {
|
|
153
|
+
NSLog(@" ⚠️ Focus failed: %d", error);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
CFRelease(cgWindowList);
|
|
157
|
+
CFRelease(windowList);
|
|
158
|
+
CFRelease(systemWide);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
CFRelease(cgWindowList);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
CFRelease(windowList);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
CFRelease(systemWide);
|
|
170
|
+
|
|
171
|
+
// Method 2: Light activation fallback (minimal app activation)
|
|
172
|
+
NSLog(@" 🔄 Trying minimal activation for window %d", windowId);
|
|
173
|
+
|
|
174
|
+
// Get window info to find the process
|
|
175
|
+
CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
|
|
176
|
+
if (cgWindowList) {
|
|
177
|
+
NSArray *windowArray = (__bridge NSArray *)cgWindowList;
|
|
178
|
+
|
|
179
|
+
for (NSDictionary *windowInfo in windowArray) {
|
|
180
|
+
NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
181
|
+
if ([cgWindowId intValue] == windowId) {
|
|
182
|
+
// Get process ID
|
|
183
|
+
NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
|
|
184
|
+
if (processId) {
|
|
185
|
+
// Light activation - only bring app to front, don't activate all windows
|
|
186
|
+
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:[processId intValue]];
|
|
187
|
+
if (app) {
|
|
188
|
+
// Use NSApplicationActivateIgnoringOtherApps only (no NSApplicationActivateAllWindows)
|
|
189
|
+
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
|
|
190
|
+
NSLog(@" ✅ App minimally activated: PID %d (specific window should be frontmost)", [processId intValue]);
|
|
191
|
+
CFRelease(cgWindowList);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
CFRelease(cgWindowList);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
|
|
203
|
+
} @catch (NSException *exception) {
|
|
204
|
+
NSLog(@"❌ Error bringing window to front: %@", exception);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
102
210
|
// Get all selectable windows
|
|
103
211
|
NSArray* getAllSelectableWindows() {
|
|
104
212
|
@autoreleasepool {
|
|
@@ -216,6 +324,17 @@ void updateOverlay() {
|
|
|
216
324
|
overlayFrame.origin.x, overlayFrame.origin.y,
|
|
217
325
|
overlayFrame.size.width, overlayFrame.size.height,
|
|
218
326
|
[g_overlayWindow level]);
|
|
327
|
+
|
|
328
|
+
// Bring window to front if enabled
|
|
329
|
+
if (g_bringToFrontEnabled) {
|
|
330
|
+
int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
|
|
331
|
+
if (windowId > 0) {
|
|
332
|
+
bool success = bringWindowToFront(windowId);
|
|
333
|
+
if (!success) {
|
|
334
|
+
NSLog(@" ⚠️ Failed to bring window to front");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
219
338
|
[g_overlayWindow setFrame:overlayFrame display:YES];
|
|
220
339
|
|
|
221
340
|
// Update overlay view window info
|
|
@@ -326,12 +445,26 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
326
445
|
g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
|
|
327
446
|
[g_overlayWindow setContentView:g_overlayView];
|
|
328
447
|
|
|
329
|
-
// Create select button
|
|
330
|
-
g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0,
|
|
448
|
+
// Create select button with blue theme
|
|
449
|
+
g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 140, 50)];
|
|
331
450
|
[g_selectButton setTitle:@"Select Window"];
|
|
332
451
|
[g_selectButton setButtonType:NSButtonTypeMomentaryPushIn];
|
|
333
452
|
[g_selectButton setBezelStyle:NSBezelStyleRounded];
|
|
334
|
-
[g_selectButton setFont:[NSFont systemFontOfSize:
|
|
453
|
+
[g_selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
|
|
454
|
+
|
|
455
|
+
// Blue themed button styling
|
|
456
|
+
[g_selectButton setWantsLayer:YES];
|
|
457
|
+
[g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
|
|
458
|
+
[g_selectButton.layer setCornerRadius:8.0];
|
|
459
|
+
[g_selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
|
|
460
|
+
[g_selectButton.layer setBorderWidth:2.0];
|
|
461
|
+
[g_selectButton setContentTintColor:[NSColor whiteColor]];
|
|
462
|
+
|
|
463
|
+
// Add shadow for better visibility
|
|
464
|
+
[g_selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
|
|
465
|
+
[g_selectButton.layer setShadowOffset:NSMakeSize(0, -2)];
|
|
466
|
+
[g_selectButton.layer setShadowRadius:4.0];
|
|
467
|
+
[g_selectButton.layer setShadowOpacity:0.3];
|
|
335
468
|
|
|
336
469
|
// Create delegate for button action and timer
|
|
337
470
|
g_delegate = [[WindowSelectorDelegate alloc] init];
|
|
@@ -441,6 +574,43 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
|
|
|
441
574
|
}
|
|
442
575
|
}
|
|
443
576
|
|
|
577
|
+
// NAPI Function: Bring Window To Front
|
|
578
|
+
Napi::Value BringWindowToFront(const Napi::CallbackInfo& info) {
|
|
579
|
+
Napi::Env env = info.Env();
|
|
580
|
+
|
|
581
|
+
if (info.Length() < 1) {
|
|
582
|
+
Napi::TypeError::New(env, "Window ID required").ThrowAsJavaScriptException();
|
|
583
|
+
return env.Null();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
int windowId = info[0].As<Napi::Number>().Int32Value();
|
|
587
|
+
|
|
588
|
+
@try {
|
|
589
|
+
bool success = bringWindowToFront(windowId);
|
|
590
|
+
return Napi::Boolean::New(env, success);
|
|
591
|
+
|
|
592
|
+
} @catch (NSException *exception) {
|
|
593
|
+
return Napi::Boolean::New(env, false);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// NAPI Function: Enable/Disable Auto Bring To Front
|
|
598
|
+
Napi::Value SetBringToFrontEnabled(const Napi::CallbackInfo& info) {
|
|
599
|
+
Napi::Env env = info.Env();
|
|
600
|
+
|
|
601
|
+
if (info.Length() < 1) {
|
|
602
|
+
Napi::TypeError::New(env, "Boolean value required").ThrowAsJavaScriptException();
|
|
603
|
+
return env.Null();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
bool enabled = info[0].As<Napi::Boolean>();
|
|
607
|
+
g_bringToFrontEnabled = enabled;
|
|
608
|
+
|
|
609
|
+
NSLog(@"🔄 Auto bring-to-front: %s", enabled ? "ENABLED" : "DISABLED");
|
|
610
|
+
|
|
611
|
+
return Napi::Boolean::New(env, true);
|
|
612
|
+
}
|
|
613
|
+
|
|
444
614
|
// NAPI Function: Get Window Selection Status
|
|
445
615
|
Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
|
|
446
616
|
Napi::Env env = info.Env();
|
|
@@ -477,6 +647,8 @@ Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
|
|
|
477
647
|
exports.Set("stopWindowSelection", Napi::Function::New(env, StopWindowSelection));
|
|
478
648
|
exports.Set("getSelectedWindowInfo", Napi::Function::New(env, GetSelectedWindowInfo));
|
|
479
649
|
exports.Set("getWindowSelectionStatus", Napi::Function::New(env, GetWindowSelectionStatus));
|
|
650
|
+
exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
|
|
651
|
+
exports.Set("setBringToFrontEnabled", Napi::Function::New(env, SetBringToFrontEnabled));
|
|
480
652
|
|
|
481
653
|
return exports;
|
|
482
654
|
}
|
package/window-selector.js
CHANGED
|
@@ -214,6 +214,41 @@ class WindowSelector extends EventEmitter {
|
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Pencereyi en öne getirir (focus yapar)
|
|
219
|
+
* @param {number} windowId - Window ID
|
|
220
|
+
* @returns {Promise<boolean>} Success/failure
|
|
221
|
+
*/
|
|
222
|
+
async bringWindowToFront(windowId) {
|
|
223
|
+
if (!windowId) {
|
|
224
|
+
throw new Error("Window ID is required");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const success = nativeBinding.bringWindowToFront(windowId);
|
|
229
|
+
return success;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
throw new Error(`Failed to bring window to front: ${error.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Otomatik pencere en öne getirme özelliğini aktif/pasif yapar
|
|
237
|
+
* Cursor hangi pencereye gelirse otomatik olarak en öne getirir
|
|
238
|
+
* @param {boolean} enabled - Enable/disable auto bring to front
|
|
239
|
+
*/
|
|
240
|
+
setBringToFrontEnabled(enabled) {
|
|
241
|
+
try {
|
|
242
|
+
nativeBinding.setBringToFrontEnabled(enabled);
|
|
243
|
+
// Only log if explicitly setting, not on startup
|
|
244
|
+
if (arguments.length > 0) {
|
|
245
|
+
console.log(`🔄 Auto bring-to-front: ${enabled ? 'ENABLED' : 'DISABLED'}`);
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw new Error(`Failed to set bring to front: ${error.message}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
217
252
|
/**
|
|
218
253
|
* Cleanup - tüm kaynakları temizle
|
|
219
254
|
*/
|