node-mac-recorder 1.3.0 → 1.4.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.
@@ -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,46 @@ if (!permissions.screenRecording) {
309
334
 
310
335
  ## 🌟 Gelişmiş Örnekler
311
336
 
337
+ ### Auto Bring-to-Front (Otomatik Focus)
338
+ ```javascript
339
+ const WindowSelector = require('./window-selector');
340
+
341
+ async function autoBringToFront() {
342
+ const selector = new WindowSelector();
343
+
344
+ // Otomatik focus modunu aktif et
345
+ selector.setBringToFrontEnabled(true);
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
+ }
354
+ ```
355
+
356
+ ### Manuel Window Focus
357
+ ```javascript
358
+ const WindowSelector = require('./window-selector');
359
+
360
+ async function manualFocus() {
361
+ const selector = new WindowSelector();
362
+
363
+ selector.on('windowEntered', async (window) => {
364
+ console.log(`Found: ${window.appName} - "${window.title}"`);
365
+
366
+ // Manuel olarak pencereyi en öne getir
367
+ const success = await selector.bringWindowToFront(window.id);
368
+ if (success) {
369
+ console.log('✅ Window brought to front!');
370
+ }
371
+ });
372
+
373
+ await selector.startSelection();
374
+ }
375
+ ```
376
+
312
377
  ### Otomatik Pencere Kaydı
313
378
  ```javascript
314
379
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
@@ -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
@@ -441,6 +560,43 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
441
560
  }
442
561
  }
443
562
 
563
+ // NAPI Function: Bring Window To Front
564
+ Napi::Value BringWindowToFront(const Napi::CallbackInfo& info) {
565
+ Napi::Env env = info.Env();
566
+
567
+ if (info.Length() < 1) {
568
+ Napi::TypeError::New(env, "Window ID required").ThrowAsJavaScriptException();
569
+ return env.Null();
570
+ }
571
+
572
+ int windowId = info[0].As<Napi::Number>().Int32Value();
573
+
574
+ @try {
575
+ bool success = bringWindowToFront(windowId);
576
+ return Napi::Boolean::New(env, success);
577
+
578
+ } @catch (NSException *exception) {
579
+ return Napi::Boolean::New(env, false);
580
+ }
581
+ }
582
+
583
+ // NAPI Function: Enable/Disable Auto Bring To Front
584
+ Napi::Value SetBringToFrontEnabled(const Napi::CallbackInfo& info) {
585
+ Napi::Env env = info.Env();
586
+
587
+ if (info.Length() < 1) {
588
+ Napi::TypeError::New(env, "Boolean value required").ThrowAsJavaScriptException();
589
+ return env.Null();
590
+ }
591
+
592
+ bool enabled = info[0].As<Napi::Boolean>();
593
+ g_bringToFrontEnabled = enabled;
594
+
595
+ NSLog(@"🔄 Auto bring-to-front: %s", enabled ? "ENABLED" : "DISABLED");
596
+
597
+ return Napi::Boolean::New(env, true);
598
+ }
599
+
444
600
  // NAPI Function: Get Window Selection Status
445
601
  Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
446
602
  Napi::Env env = info.Env();
@@ -477,6 +633,8 @@ Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
477
633
  exports.Set("stopWindowSelection", Napi::Function::New(env, StopWindowSelection));
478
634
  exports.Set("getSelectedWindowInfo", Napi::Function::New(env, GetSelectedWindowInfo));
479
635
  exports.Set("getWindowSelectionStatus", Napi::Function::New(env, GetWindowSelectionStatus));
636
+ exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
637
+ exports.Set("setBringToFrontEnabled", Napi::Function::New(env, SetBringToFrontEnabled));
480
638
 
481
639
  return exports;
482
640
  }
@@ -214,6 +214,38 @@ 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
+ console.log(`🔄 Auto bring-to-front: ${enabled ? 'ENABLED' : 'DISABLED'}`);
244
+ } catch (error) {
245
+ throw new Error(`Failed to set bring to front: ${error.message}`);
246
+ }
247
+ }
248
+
217
249
  /**
218
250
  * Cleanup - tüm kaynakları temizle
219
251
  */