node-mac-recorder 2.1.2 → 2.2.1

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.
@@ -0,0 +1,1457 @@
1
+ #import <napi.h>
2
+ #import <AppKit/AppKit.h>
3
+ #import <Foundation/Foundation.h>
4
+ #import <CoreGraphics/CoreGraphics.h>
5
+ #import <ApplicationServices/ApplicationServices.h>
6
+ #import <Carbon/Carbon.h>
7
+ #import <Accessibility/Accessibility.h>
8
+
9
+ // Global state for window selection
10
+ static bool g_isWindowSelecting = false;
11
+ static NSWindow *g_overlayWindow = nil;
12
+ static NSView *g_overlayView = nil;
13
+ static NSButton *g_selectButton = nil;
14
+ static NSTimer *g_trackingTimer = nil;
15
+ static NSDictionary *g_selectedWindowInfo = nil;
16
+ static NSMutableArray *g_allWindows = nil;
17
+ static NSDictionary *g_currentWindowUnderCursor = nil;
18
+ static bool g_bringToFrontEnabled = true; // Default enabled
19
+ static id g_windowKeyEventMonitor = nil;
20
+
21
+ // Recording preview overlay state
22
+ static NSWindow *g_recordingPreviewWindow = nil;
23
+ static NSView *g_recordingPreviewView = nil;
24
+ static NSDictionary *g_recordingWindowInfo = nil;
25
+
26
+ // Screen selection overlay state
27
+ static bool g_isScreenSelecting = false;
28
+ static NSMutableArray *g_screenOverlayWindows = nil;
29
+ static NSDictionary *g_selectedScreenInfo = nil;
30
+ static NSArray *g_allScreens = nil;
31
+ static id g_screenKeyEventMonitor = nil;
32
+
33
+ // Forward declarations
34
+ void cleanupWindowSelector();
35
+ void updateOverlay();
36
+ NSDictionary* getWindowUnderCursor(CGPoint point);
37
+ NSArray* getAllSelectableWindows();
38
+ bool bringWindowToFront(int windowId);
39
+ void cleanupRecordingPreview();
40
+ bool showRecordingPreview(NSDictionary *windowInfo);
41
+ bool hideRecordingPreview();
42
+ void cleanupScreenSelector();
43
+ bool startScreenSelection();
44
+ bool stopScreenSelection();
45
+ NSDictionary* getSelectedScreenInfo();
46
+ bool showScreenRecordingPreview(NSDictionary *screenInfo);
47
+ bool hideScreenRecordingPreview();
48
+
49
+ // Custom overlay view class
50
+ @interface WindowSelectorOverlayView : NSView
51
+ @property (nonatomic, strong) NSDictionary *windowInfo;
52
+ @end
53
+
54
+ @implementation WindowSelectorOverlayView
55
+
56
+ - (instancetype)initWithFrame:(NSRect)frameRect {
57
+ self = [super initWithFrame:frameRect];
58
+ if (self) {
59
+ self.wantsLayer = YES;
60
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] CGColor];
61
+ self.layer.borderColor = [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor];
62
+ self.layer.borderWidth = 5.0;
63
+ self.layer.cornerRadius = 8.0;
64
+ }
65
+ return self;
66
+ }
67
+
68
+ - (void)drawRect:(NSRect)dirtyRect {
69
+ [super drawRect:dirtyRect];
70
+
71
+ if (!self.windowInfo) return;
72
+
73
+ // Background with transparency
74
+ [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] setFill];
75
+ NSRectFill(dirtyRect);
76
+
77
+ // Border
78
+ [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] setStroke];
79
+ NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:8 yRadius:8];
80
+ [border setLineWidth:3.0];
81
+ [border stroke];
82
+
83
+ // Window info text
84
+ NSString *windowTitle = [self.windowInfo objectForKey:@"title"] ?: @"Unknown Window";
85
+ NSString *appName = [self.windowInfo objectForKey:@"appName"] ?: @"Unknown App";
86
+ NSString *infoText = [NSString stringWithFormat:@"%@\n%@", appName, windowTitle];
87
+
88
+ NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
89
+ [style setAlignment:NSTextAlignmentCenter];
90
+
91
+ NSDictionary *attributes = @{
92
+ NSFontAttributeName: [NSFont systemFontOfSize:21 weight:NSFontWeightMedium],
93
+ NSForegroundColorAttributeName: [NSColor whiteColor],
94
+ NSParagraphStyleAttributeName: style,
95
+ NSStrokeColorAttributeName: [NSColor blackColor],
96
+ NSStrokeWidthAttributeName: @(-2.0)
97
+ };
98
+
99
+ NSRect textRect = NSMakeRect(10, self.bounds.size.height - 90, self.bounds.size.width - 20, 80);
100
+ [infoText drawInRect:textRect withAttributes:attributes];
101
+ }
102
+
103
+ @end
104
+
105
+ // Recording preview overlay view - full screen with cutout
106
+ @interface RecordingPreviewView : NSView
107
+ @property (nonatomic, strong) NSDictionary *recordingWindowInfo;
108
+ @end
109
+
110
+ @implementation RecordingPreviewView
111
+
112
+ - (instancetype)initWithFrame:(NSRect)frameRect {
113
+ self = [super initWithFrame:frameRect];
114
+ if (self) {
115
+ self.wantsLayer = YES;
116
+ self.layer.backgroundColor = [[NSColor clearColor] CGColor];
117
+ }
118
+ return self;
119
+ }
120
+
121
+ - (void)drawRect:(NSRect)dirtyRect {
122
+ [super drawRect:dirtyRect];
123
+
124
+ if (!self.recordingWindowInfo) {
125
+ // No window info, fill with semi-transparent black
126
+ [[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5] setFill];
127
+ NSRectFill(dirtyRect);
128
+ return;
129
+ }
130
+
131
+ // Get window coordinates
132
+ int windowX = [[self.recordingWindowInfo objectForKey:@"x"] intValue];
133
+ int windowY = [[self.recordingWindowInfo objectForKey:@"y"] intValue];
134
+ int windowWidth = [[self.recordingWindowInfo objectForKey:@"width"] intValue];
135
+ int windowHeight = [[self.recordingWindowInfo objectForKey:@"height"] intValue];
136
+
137
+ // Convert from CGWindow coordinates (top-left) to NSView coordinates (bottom-left)
138
+ NSScreen *mainScreen = [NSScreen mainScreen];
139
+ CGFloat screenHeight = [mainScreen frame].size.height;
140
+ CGFloat convertedY = screenHeight - windowY - windowHeight;
141
+
142
+ NSRect windowRect = NSMakeRect(windowX, convertedY, windowWidth, windowHeight);
143
+
144
+ // Create a path that covers the entire view but excludes the window area
145
+ NSBezierPath *maskPath = [NSBezierPath bezierPathWithRect:self.bounds];
146
+ NSBezierPath *windowPath = [NSBezierPath bezierPathWithRect:windowRect];
147
+ [maskPath appendBezierPath:windowPath];
148
+ [maskPath setWindingRule:NSWindingRuleEvenOdd]; // Creates hole effect
149
+
150
+ // Fill with semi-transparent black, excluding window area
151
+ [[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5] setFill];
152
+ [maskPath fill];
153
+ }
154
+
155
+ @end
156
+
157
+ // Screen selection overlay view
158
+ @interface ScreenSelectorOverlayView : NSView
159
+ @property (nonatomic, strong) NSDictionary *screenInfo;
160
+ @end
161
+
162
+ @implementation ScreenSelectorOverlayView
163
+
164
+ - (instancetype)initWithFrame:(NSRect)frameRect {
165
+ self = [super initWithFrame:frameRect];
166
+ if (self) {
167
+ self.wantsLayer = YES;
168
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] CGColor];
169
+ self.layer.borderColor = [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor];
170
+ self.layer.borderWidth = 5.0;
171
+ self.layer.cornerRadius = 8.0;
172
+ }
173
+ return self;
174
+ }
175
+
176
+ - (void)drawRect:(NSRect)dirtyRect {
177
+ [super drawRect:dirtyRect];
178
+
179
+ if (!self.screenInfo) return;
180
+
181
+ // Background with transparency
182
+ [[NSColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.45] setFill];
183
+ NSRectFill(dirtyRect);
184
+
185
+ // Border
186
+ [[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] setStroke];
187
+ NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:8 yRadius:8];
188
+ [border setLineWidth:3.0];
189
+ [border stroke];
190
+
191
+ // Screen info text
192
+ NSString *screenName = [self.screenInfo objectForKey:@"name"] ?: @"Unknown Screen";
193
+ NSString *resolution = [self.screenInfo objectForKey:@"resolution"] ?: @"Unknown Resolution";
194
+ NSString *infoText = [NSString stringWithFormat:@"%@\n%@", screenName, resolution];
195
+
196
+ NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
197
+ [style setAlignment:NSTextAlignmentCenter];
198
+
199
+ NSDictionary *attributes = @{
200
+ NSFontAttributeName: [NSFont systemFontOfSize:21 weight:NSFontWeightMedium],
201
+ NSForegroundColorAttributeName: [NSColor whiteColor],
202
+ NSParagraphStyleAttributeName: style,
203
+ NSStrokeColorAttributeName: [NSColor blackColor],
204
+ NSStrokeWidthAttributeName: @(-2.0)
205
+ };
206
+
207
+ NSRect textRect = NSMakeRect(10, self.bounds.size.height - 90, self.bounds.size.width - 20, 80);
208
+ [infoText drawInRect:textRect withAttributes:attributes];
209
+ }
210
+
211
+ @end
212
+
213
+ // Button action handler and timer target
214
+ @interface WindowSelectorDelegate : NSObject
215
+ - (void)selectButtonClicked:(id)sender;
216
+ - (void)screenSelectButtonClicked:(id)sender;
217
+ - (void)cancelButtonClicked:(id)sender;
218
+ - (void)timerUpdate:(NSTimer *)timer;
219
+ @end
220
+
221
+ @implementation WindowSelectorDelegate
222
+ - (void)selectButtonClicked:(id)sender {
223
+ if (g_currentWindowUnderCursor) {
224
+ g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
225
+ cleanupWindowSelector();
226
+ }
227
+ }
228
+
229
+ - (void)screenSelectButtonClicked:(id)sender {
230
+ NSButton *button = (NSButton *)sender;
231
+ NSInteger screenIndex = [button tag];
232
+
233
+ // Get screen info from global array using button tag
234
+ if (g_allScreens && screenIndex >= 0 && screenIndex < [g_allScreens count]) {
235
+ NSDictionary *screenInfo = [g_allScreens objectAtIndex:screenIndex];
236
+ g_selectedScreenInfo = [screenInfo retain];
237
+
238
+ NSLog(@"🖥️ SCREEN BUTTON CLICKED: %@ (%@)",
239
+ [screenInfo objectForKey:@"name"],
240
+ [screenInfo objectForKey:@"resolution"]);
241
+
242
+ cleanupScreenSelector();
243
+ }
244
+ }
245
+
246
+ - (void)cancelButtonClicked:(id)sender {
247
+ NSLog(@"🚫 CANCEL BUTTON CLICKED: Selection cancelled");
248
+ // Clean up without selecting anything
249
+ if (g_isScreenSelecting) {
250
+ cleanupScreenSelector();
251
+ } else {
252
+ cleanupWindowSelector();
253
+ }
254
+ }
255
+
256
+ - (void)timerUpdate:(NSTimer *)timer {
257
+ updateOverlay();
258
+ }
259
+ @end
260
+
261
+ static WindowSelectorDelegate *g_delegate = nil;
262
+
263
+ // Bring window to front using Accessibility API
264
+ bool bringWindowToFront(int windowId) {
265
+ @autoreleasepool {
266
+ @try {
267
+ // Method 1: Using Accessibility API (most reliable)
268
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
269
+ if (!systemWide) return false;
270
+
271
+ CFArrayRef windowList = NULL;
272
+ AXError error = AXUIElementCopyAttributeValue(systemWide, kAXWindowsAttribute, (CFTypeRef*)&windowList);
273
+
274
+ if (error == kAXErrorSuccess && windowList) {
275
+ CFIndex windowCount = CFArrayGetCount(windowList);
276
+
277
+ for (CFIndex i = 0; i < windowCount; i++) {
278
+ AXUIElementRef windowElement = (AXUIElementRef)CFArrayGetValueAtIndex(windowList, i);
279
+
280
+ // Get window ID by comparing with CGWindowList
281
+ // Since _AXUIElementGetWindow is not available, we'll use app PID approach
282
+ pid_t windowPid;
283
+ error = AXUIElementGetPid(windowElement, &windowPid);
284
+
285
+ if (error == kAXErrorSuccess) {
286
+ // Get window info for this PID from CGWindowList
287
+ CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
288
+ if (cgWindowList) {
289
+ NSArray *windowArray = (__bridge NSArray *)cgWindowList;
290
+
291
+ for (NSDictionary *windowInfo in windowArray) {
292
+ NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
293
+ NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
294
+
295
+ if ([cgWindowId intValue] == windowId && [processId intValue] == windowPid) {
296
+ // Found the window, bring it to front
297
+ NSLog(@"🔝 BRINGING TO FRONT: Window ID %d (PID: %d)", windowId, windowPid);
298
+
299
+ // Method 1: Raise specific window (not the whole app)
300
+ error = AXUIElementPerformAction(windowElement, kAXRaiseAction);
301
+ if (error == kAXErrorSuccess) {
302
+ NSLog(@" ✅ Specific window raised successfully");
303
+ } else {
304
+ NSLog(@" ⚠️ Raise action failed: %d", error);
305
+ }
306
+
307
+ // Method 2: Focus specific window (not main window)
308
+ error = AXUIElementSetAttributeValue(windowElement, kAXFocusedAttribute, kCFBooleanTrue);
309
+ if (error == kAXErrorSuccess) {
310
+ NSLog(@" ✅ Specific window focused");
311
+ } else {
312
+ NSLog(@" ⚠️ Focus failed: %d", error);
313
+ }
314
+
315
+ CFRelease(cgWindowList);
316
+ CFRelease(windowList);
317
+ CFRelease(systemWide);
318
+ return true;
319
+ }
320
+ }
321
+ CFRelease(cgWindowList);
322
+ }
323
+ }
324
+ }
325
+ CFRelease(windowList);
326
+ }
327
+
328
+ CFRelease(systemWide);
329
+
330
+ // Method 2: Light activation fallback (minimal app activation)
331
+ NSLog(@" 🔄 Trying minimal activation for window %d", windowId);
332
+
333
+ // Get window info to find the process
334
+ CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
335
+ if (cgWindowList) {
336
+ NSArray *windowArray = (__bridge NSArray *)cgWindowList;
337
+
338
+ for (NSDictionary *windowInfo in windowArray) {
339
+ NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
340
+ if ([cgWindowId intValue] == windowId) {
341
+ // Get process ID
342
+ NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
343
+ if (processId) {
344
+ // Light activation - only bring app to front, don't activate all windows
345
+ NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:[processId intValue]];
346
+ if (app) {
347
+ // Use NSApplicationActivateIgnoringOtherApps only (no NSApplicationActivateAllWindows)
348
+ [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
349
+ NSLog(@" ✅ App minimally activated: PID %d (specific window should be frontmost)", [processId intValue]);
350
+ CFRelease(cgWindowList);
351
+ return true;
352
+ }
353
+ }
354
+ break;
355
+ }
356
+ }
357
+ CFRelease(cgWindowList);
358
+ }
359
+
360
+ return false;
361
+
362
+ } @catch (NSException *exception) {
363
+ NSLog(@"❌ Error bringing window to front: %@", exception);
364
+ return false;
365
+ }
366
+ }
367
+ }
368
+
369
+ // Get all selectable windows
370
+ NSArray* getAllSelectableWindows() {
371
+ @autoreleasepool {
372
+ NSMutableArray *windows = [NSMutableArray array];
373
+
374
+ // Get all windows using CGWindowListCopyWindowInfo
375
+ CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
376
+
377
+ if (windowList) {
378
+ NSArray *windowArray = (__bridge NSArray *)windowList;
379
+
380
+ for (NSDictionary *windowInfo in windowArray) {
381
+ NSString *windowOwner = [windowInfo objectForKey:(NSString *)kCGWindowOwnerName];
382
+ NSString *windowName = [windowInfo objectForKey:(NSString *)kCGWindowName];
383
+ NSNumber *windowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
384
+ NSNumber *windowLayer = [windowInfo objectForKey:(NSString *)kCGWindowLayer];
385
+ NSDictionary *bounds = [windowInfo objectForKey:(NSString *)kCGWindowBounds];
386
+
387
+ // Skip system windows, dock, menu bar, etc.
388
+ if ([windowLayer intValue] != 0) continue; // Only normal windows
389
+ if (!windowOwner || [windowOwner length] == 0) continue;
390
+ if ([windowOwner isEqualToString:@"WindowServer"]) continue;
391
+ if ([windowOwner isEqualToString:@"Dock"]) continue;
392
+
393
+ // Extract bounds
394
+ int x = [[bounds objectForKey:@"X"] intValue];
395
+ int y = [[bounds objectForKey:@"Y"] intValue];
396
+ int width = [[bounds objectForKey:@"Width"] intValue];
397
+ int height = [[bounds objectForKey:@"Height"] intValue];
398
+
399
+ // Skip too small windows
400
+ if (width < 50 || height < 50) continue;
401
+
402
+ NSDictionary *window = @{
403
+ @"id": windowId ?: @(0),
404
+ @"title": windowName ?: @"Untitled",
405
+ @"appName": windowOwner,
406
+ @"x": @(x),
407
+ @"y": @(y),
408
+ @"width": @(width),
409
+ @"height": @(height)
410
+ };
411
+
412
+ [windows addObject:window];
413
+ }
414
+
415
+ CFRelease(windowList);
416
+ }
417
+
418
+ return [windows copy];
419
+ }
420
+ }
421
+
422
+ // Get window under cursor point
423
+ NSDictionary* getWindowUnderCursor(CGPoint point) {
424
+ @autoreleasepool {
425
+ if (!g_allWindows) return nil;
426
+
427
+ // Find window that contains the cursor point
428
+ for (NSDictionary *window in g_allWindows) {
429
+ int x = [[window objectForKey:@"x"] intValue];
430
+ int y = [[window objectForKey:@"y"] intValue];
431
+ int width = [[window objectForKey:@"width"] intValue];
432
+ int height = [[window objectForKey:@"height"] intValue];
433
+
434
+ if (point.x >= x && point.x <= x + width &&
435
+ point.y >= y && point.y <= y + height) {
436
+ return window;
437
+ }
438
+ }
439
+
440
+ return nil;
441
+ }
442
+ }
443
+
444
+ // Update overlay to highlight window under cursor
445
+ void updateOverlay() {
446
+ @autoreleasepool {
447
+ if (!g_isWindowSelecting || !g_overlayWindow) return;
448
+
449
+ // Get current cursor position
450
+ NSPoint mouseLocation = [NSEvent mouseLocation];
451
+ // Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
452
+ NSScreen *mainScreen = [NSScreen mainScreen];
453
+ CGFloat screenHeight = [mainScreen frame].size.height;
454
+ CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
455
+
456
+ // Find window under cursor
457
+ NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
458
+
459
+ if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
460
+ // Update current window
461
+ [g_currentWindowUnderCursor release];
462
+ g_currentWindowUnderCursor = [windowUnderCursor retain];
463
+
464
+ // Update overlay position and size
465
+ int x = [[windowUnderCursor objectForKey:@"x"] intValue];
466
+ int y = [[windowUnderCursor objectForKey:@"y"] intValue];
467
+ int width = [[windowUnderCursor objectForKey:@"width"] intValue];
468
+ int height = [[windowUnderCursor objectForKey:@"height"] intValue];
469
+
470
+ // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left)
471
+ NSScreen *mainScreen = [NSScreen mainScreen];
472
+ CGFloat screenHeight = [mainScreen frame].size.height;
473
+ CGFloat adjustedY = screenHeight - y - height;
474
+
475
+ // Clamp overlay to screen bounds to avoid partial off-screen issues
476
+ NSRect screenFrame = [mainScreen frame];
477
+ CGFloat clampedX = MAX(screenFrame.origin.x, MIN(x, screenFrame.origin.x + screenFrame.size.width - width));
478
+ CGFloat clampedY = MAX(screenFrame.origin.y, MIN(adjustedY, screenFrame.origin.y + screenFrame.size.height - height));
479
+ CGFloat clampedWidth = MIN(width, screenFrame.size.width - (clampedX - screenFrame.origin.x));
480
+ CGFloat clampedHeight = MIN(height, screenFrame.size.height - (clampedY - screenFrame.origin.y));
481
+
482
+ NSRect overlayFrame = NSMakeRect(clampedX, clampedY, clampedWidth, clampedHeight);
483
+
484
+ NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
485
+ NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
486
+
487
+ NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
488
+ NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
489
+ NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
490
+ overlayFrame.origin.x, overlayFrame.origin.y,
491
+ overlayFrame.size.width, overlayFrame.size.height,
492
+ [g_overlayWindow level]);
493
+
494
+ // Bring window to front if enabled
495
+ if (g_bringToFrontEnabled) {
496
+ int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
497
+ if (windowId > 0) {
498
+ bool success = bringWindowToFront(windowId);
499
+ if (!success) {
500
+ NSLog(@" ⚠️ Failed to bring window to front");
501
+ }
502
+ }
503
+ }
504
+ [g_overlayWindow setFrame:overlayFrame display:YES];
505
+
506
+ // Update overlay view window info
507
+ [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
508
+ [g_overlayView setNeedsDisplay:YES];
509
+
510
+ // Position buttons - Start Record in center, Cancel below it
511
+ if (g_selectButton) {
512
+ NSSize buttonSize = [g_selectButton frame].size;
513
+ NSPoint buttonCenter = NSMakePoint(
514
+ (width - buttonSize.width) / 2,
515
+ (height - buttonSize.height) / 2 + 30 // Slightly above center
516
+ );
517
+ [g_selectButton setFrameOrigin:buttonCenter];
518
+
519
+ // Position cancel button below the main button
520
+ NSButton *cancelButton = nil;
521
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
522
+ if ([subview isKindOfClass:[NSButton class]] &&
523
+ [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
524
+ cancelButton = (NSButton*)subview;
525
+ break;
526
+ }
527
+ }
528
+
529
+ if (cancelButton) {
530
+ NSSize cancelButtonSize = [cancelButton frame].size;
531
+ NSPoint cancelButtonCenter = NSMakePoint(
532
+ (width - cancelButtonSize.width) / 2,
533
+ buttonCenter.y - buttonSize.height - 20 // 20px below main button
534
+ );
535
+ [cancelButton setFrameOrigin:cancelButtonCenter];
536
+ }
537
+ }
538
+
539
+ [g_overlayWindow orderFront:nil];
540
+ [g_overlayWindow makeKeyAndOrderFront:nil];
541
+
542
+ NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
543
+ [g_overlayWindow level], [g_overlayWindow alphaValue],
544
+ [g_overlayWindow isVisible] ? "YES" : "NO");
545
+ } else if (!windowUnderCursor && g_currentWindowUnderCursor) {
546
+ // No window under cursor, hide overlay
547
+ NSString *leftWindowTitle = [g_currentWindowUnderCursor objectForKey:@"title"] ?: @"Untitled";
548
+ NSString *leftAppName = [g_currentWindowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
549
+
550
+ NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
551
+
552
+ [g_overlayWindow orderOut:nil];
553
+ [g_currentWindowUnderCursor release];
554
+ g_currentWindowUnderCursor = nil;
555
+ }
556
+ }
557
+ }
558
+
559
+ // Cleanup function
560
+ void cleanupWindowSelector() {
561
+ g_isWindowSelecting = false;
562
+
563
+ // Stop tracking timer
564
+ if (g_trackingTimer) {
565
+ [g_trackingTimer invalidate];
566
+ g_trackingTimer = nil;
567
+ }
568
+
569
+ // Remove key event monitor
570
+ if (g_windowKeyEventMonitor) {
571
+ [NSEvent removeMonitor:g_windowKeyEventMonitor];
572
+ g_windowKeyEventMonitor = nil;
573
+ }
574
+
575
+ // Close overlay window
576
+ if (g_overlayWindow) {
577
+ [g_overlayWindow close];
578
+ g_overlayWindow = nil;
579
+ g_overlayView = nil;
580
+ g_selectButton = nil;
581
+ }
582
+
583
+ // Clean up delegate
584
+ if (g_delegate) {
585
+ [g_delegate release];
586
+ g_delegate = nil;
587
+ }
588
+
589
+ // Clean up data
590
+ if (g_allWindows) {
591
+ [g_allWindows release];
592
+ g_allWindows = nil;
593
+ }
594
+
595
+ if (g_currentWindowUnderCursor) {
596
+ [g_currentWindowUnderCursor release];
597
+ g_currentWindowUnderCursor = nil;
598
+ }
599
+ }
600
+
601
+ // Recording preview functions
602
+ void cleanupRecordingPreview() {
603
+ if (g_recordingPreviewWindow) {
604
+ [g_recordingPreviewWindow close];
605
+ g_recordingPreviewWindow = nil;
606
+ g_recordingPreviewView = nil;
607
+ }
608
+
609
+ if (g_recordingWindowInfo) {
610
+ [g_recordingWindowInfo release];
611
+ g_recordingWindowInfo = nil;
612
+ }
613
+ }
614
+
615
+ bool showRecordingPreview(NSDictionary *windowInfo) {
616
+ @try {
617
+ // Clean up any existing preview
618
+ cleanupRecordingPreview();
619
+
620
+ if (!windowInfo) return false;
621
+
622
+ // Store window info
623
+ g_recordingWindowInfo = [windowInfo retain];
624
+
625
+ // Get main screen bounds for full screen overlay
626
+ NSScreen *mainScreen = [NSScreen mainScreen];
627
+ NSRect screenFrame = [mainScreen frame];
628
+
629
+ // Create full-screen overlay window
630
+ g_recordingPreviewWindow = [[NSWindow alloc] initWithContentRect:screenFrame
631
+ styleMask:NSWindowStyleMaskBorderless
632
+ backing:NSBackingStoreBuffered
633
+ defer:NO];
634
+
635
+ [g_recordingPreviewWindow setLevel:CGWindowLevelForKey(kCGOverlayWindowLevelKey)]; // High level but below selection
636
+ [g_recordingPreviewWindow setOpaque:NO];
637
+ [g_recordingPreviewWindow setBackgroundColor:[NSColor clearColor]];
638
+ [g_recordingPreviewWindow setIgnoresMouseEvents:YES]; // Don't interfere with user interaction
639
+ [g_recordingPreviewWindow setAcceptsMouseMovedEvents:NO];
640
+ [g_recordingPreviewWindow setHasShadow:NO];
641
+ [g_recordingPreviewWindow setAlphaValue:1.0];
642
+ [g_recordingPreviewWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
643
+
644
+ // Create preview view
645
+ g_recordingPreviewView = [[RecordingPreviewView alloc] initWithFrame:screenFrame];
646
+ [(RecordingPreviewView *)g_recordingPreviewView setRecordingWindowInfo:windowInfo];
647
+ [g_recordingPreviewWindow setContentView:g_recordingPreviewView];
648
+
649
+ // Show the preview
650
+ [g_recordingPreviewWindow orderFront:nil];
651
+ [g_recordingPreviewWindow makeKeyAndOrderFront:nil];
652
+
653
+ NSLog(@"🎬 RECORDING PREVIEW: Showing overlay for %@ - \"%@\"",
654
+ [windowInfo objectForKey:@"appName"],
655
+ [windowInfo objectForKey:@"title"]);
656
+
657
+ return true;
658
+
659
+ } @catch (NSException *exception) {
660
+ NSLog(@"❌ Error showing recording preview: %@", exception);
661
+ cleanupRecordingPreview();
662
+ return false;
663
+ }
664
+ }
665
+
666
+ bool hideRecordingPreview() {
667
+ @try {
668
+ if (g_recordingPreviewWindow) {
669
+ NSLog(@"🎬 RECORDING PREVIEW: Hiding overlay");
670
+ cleanupRecordingPreview();
671
+ return true;
672
+ }
673
+ return false;
674
+
675
+ } @catch (NSException *exception) {
676
+ NSLog(@"❌ Error hiding recording preview: %@", exception);
677
+ return false;
678
+ }
679
+ }
680
+
681
+ // Screen selection functions
682
+ void cleanupScreenSelector() {
683
+ g_isScreenSelecting = false;
684
+
685
+ // Remove key event monitor
686
+ if (g_screenKeyEventMonitor) {
687
+ [NSEvent removeMonitor:g_screenKeyEventMonitor];
688
+ g_screenKeyEventMonitor = nil;
689
+ }
690
+
691
+ // Close all screen overlay windows
692
+ if (g_screenOverlayWindows) {
693
+ for (NSWindow *overlayWindow in g_screenOverlayWindows) {
694
+ [overlayWindow close];
695
+ }
696
+ [g_screenOverlayWindows release];
697
+ g_screenOverlayWindows = nil;
698
+ }
699
+
700
+ // Clean up screen data
701
+ if (g_allScreens) {
702
+ [g_allScreens release];
703
+ g_allScreens = nil;
704
+ }
705
+ }
706
+
707
+ bool startScreenSelection() {
708
+ @try {
709
+ if (g_isScreenSelecting) return false;
710
+
711
+ // Get all available screens
712
+ NSArray *screens = [NSScreen screens];
713
+ if (!screens || [screens count] == 0) return false;
714
+
715
+ // Create screen info array
716
+ NSMutableArray *screenInfoArray = [[NSMutableArray alloc] init];
717
+ g_screenOverlayWindows = [[NSMutableArray alloc] init];
718
+
719
+ for (NSInteger i = 0; i < [screens count]; i++) {
720
+ NSScreen *screen = [screens objectAtIndex:i];
721
+ NSRect screenFrame = [screen frame];
722
+
723
+ // Create screen info dictionary
724
+ NSMutableDictionary *screenInfo = [[NSMutableDictionary alloc] init];
725
+ [screenInfo setObject:[NSNumber numberWithInteger:i] forKey:@"id"];
726
+ [screenInfo setObject:[NSString stringWithFormat:@"Display %ld", (long)(i + 1)] forKey:@"name"];
727
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.origin.x] forKey:@"x"];
728
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.origin.y] forKey:@"y"];
729
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.size.width] forKey:@"width"];
730
+ [screenInfo setObject:[NSNumber numberWithInt:(int)screenFrame.size.height] forKey:@"height"];
731
+ [screenInfo setObject:[NSString stringWithFormat:@"%.0fx%.0f", screenFrame.size.width, screenFrame.size.height] forKey:@"resolution"];
732
+ [screenInfo setObject:[NSNumber numberWithBool:(i == 0)] forKey:@"isPrimary"]; // First screen is primary
733
+ [screenInfoArray addObject:screenInfo];
734
+
735
+ // Create overlay window for this screen (FULL screen including menu bar)
736
+ NSWindow *overlayWindow = [[NSWindow alloc] initWithContentRect:screenFrame
737
+ styleMask:NSWindowStyleMaskBorderless
738
+ backing:NSBackingStoreBuffered
739
+ defer:NO
740
+ screen:screen];
741
+
742
+ [overlayWindow setLevel:CGWindowLevelForKey(kCGMaximumWindowLevelKey)];
743
+ [overlayWindow setOpaque:NO];
744
+ [overlayWindow setBackgroundColor:[NSColor clearColor]];
745
+ [overlayWindow setIgnoresMouseEvents:NO];
746
+ [overlayWindow setAcceptsMouseMovedEvents:YES];
747
+ [overlayWindow setHasShadow:NO];
748
+ [overlayWindow setAlphaValue:1.0];
749
+ [overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
750
+
751
+ // Create overlay view
752
+ ScreenSelectorOverlayView *overlayView = [[ScreenSelectorOverlayView alloc] initWithFrame:screenFrame];
753
+ [overlayView setScreenInfo:screenInfo];
754
+ [overlayWindow setContentView:overlayView];
755
+
756
+ // Create select button
757
+ NSButton *selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 180, 60)];
758
+ [selectButton setTitle:@"Start Record"];
759
+ [selectButton setButtonType:NSButtonTypeMomentaryPushIn];
760
+ [selectButton setBezelStyle:NSBezelStyleRounded];
761
+ [selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
762
+ [selectButton setTag:i]; // Set screen index as tag
763
+
764
+ // Blue background with white text
765
+ [selectButton setWantsLayer:YES];
766
+ [selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
767
+ [selectButton.layer setCornerRadius:8.0];
768
+ [selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
769
+ [selectButton.layer setBorderWidth:2.0];
770
+
771
+ // White text color
772
+ NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
773
+ initWithString:[selectButton title]];
774
+ [titleString addAttribute:NSForegroundColorAttributeName
775
+ value:[NSColor whiteColor]
776
+ range:NSMakeRange(0, [titleString length])];
777
+ [selectButton setAttributedTitle:titleString];
778
+
779
+ // Add shadow for better visibility
780
+ [selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
781
+ [selectButton.layer setShadowOffset:NSMakeSize(0, -2)];
782
+ [selectButton.layer setShadowRadius:4.0];
783
+ [selectButton.layer setShadowOpacity:0.3];
784
+
785
+ // Set button target and action (reuse global delegate)
786
+ if (!g_delegate) {
787
+ g_delegate = [[WindowSelectorDelegate alloc] init];
788
+ }
789
+ [selectButton setTarget:g_delegate];
790
+ [selectButton setAction:@selector(screenSelectButtonClicked:)];
791
+
792
+ // Create cancel button for screen selection
793
+ NSButton *screenCancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
794
+ [screenCancelButton setTitle:@"Cancel"];
795
+ [screenCancelButton setButtonType:NSButtonTypeMomentaryPushIn];
796
+ [screenCancelButton setBezelStyle:NSBezelStyleRounded];
797
+ [screenCancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightMedium]];
798
+
799
+ // Gray cancel button styling
800
+ [screenCancelButton setWantsLayer:YES];
801
+ [screenCancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.8] CGColor]];
802
+ [screenCancelButton.layer setCornerRadius:6.0];
803
+ [screenCancelButton.layer setBorderColor:[[NSColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor]];
804
+ [screenCancelButton.layer setBorderWidth:1.0];
805
+
806
+ // White text for cancel button
807
+ NSMutableAttributedString *screenCancelTitleString = [[NSMutableAttributedString alloc]
808
+ initWithString:[screenCancelButton title]];
809
+ [screenCancelTitleString addAttribute:NSForegroundColorAttributeName
810
+ value:[NSColor whiteColor]
811
+ range:NSMakeRange(0, [screenCancelTitleString length])];
812
+ [screenCancelButton setAttributedTitle:screenCancelTitleString];
813
+
814
+ // Add shadow for cancel button
815
+ [screenCancelButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
816
+ [screenCancelButton.layer setShadowOffset:NSMakeSize(0, -1)];
817
+ [screenCancelButton.layer setShadowRadius:2.0];
818
+ [screenCancelButton.layer setShadowOpacity:0.2];
819
+
820
+ [screenCancelButton setTarget:g_delegate];
821
+ [screenCancelButton setAction:@selector(cancelButtonClicked:)];
822
+
823
+ // Position buttons - Start Record in center, Cancel below it
824
+ NSPoint buttonCenter = NSMakePoint(
825
+ (screenFrame.size.width - [selectButton frame].size.width) / 2,
826
+ (screenFrame.size.height - [selectButton frame].size.height) / 2 + 30 // Slightly above center
827
+ );
828
+ [selectButton setFrameOrigin:buttonCenter];
829
+
830
+ NSPoint cancelButtonCenter = NSMakePoint(
831
+ (screenFrame.size.width - [screenCancelButton frame].size.width) / 2,
832
+ buttonCenter.y - [selectButton frame].size.height - 20 // 20px below main button
833
+ );
834
+ [screenCancelButton setFrameOrigin:cancelButtonCenter];
835
+
836
+ [overlayView addSubview:selectButton];
837
+ [overlayView addSubview:screenCancelButton];
838
+ [overlayWindow orderFront:nil];
839
+ [overlayWindow makeKeyAndOrderFront:nil];
840
+
841
+ [g_screenOverlayWindows addObject:overlayWindow];
842
+ [screenInfo release];
843
+ }
844
+
845
+ g_allScreens = [screenInfoArray retain];
846
+ [screenInfoArray release];
847
+ g_isScreenSelecting = true;
848
+
849
+ // Add ESC key event monitor to cancel selection
850
+ g_screenKeyEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown
851
+ handler:^(NSEvent *event) {
852
+ if ([event keyCode] == 53) { // ESC key
853
+ NSLog(@"🖥️ SCREEN SELECTION: ESC pressed - cancelling selection");
854
+ cleanupScreenSelector();
855
+ }
856
+ }];
857
+
858
+ NSLog(@"🖥️ SCREEN SELECTION: Started with %lu screens (ESC to cancel)", (unsigned long)[screens count]);
859
+
860
+ return true;
861
+
862
+ } @catch (NSException *exception) {
863
+ NSLog(@"❌ Error starting screen selection: %@", exception);
864
+ cleanupScreenSelector();
865
+ return false;
866
+ }
867
+ }
868
+
869
+ bool stopScreenSelection() {
870
+ @try {
871
+ if (!g_isScreenSelecting) return false;
872
+
873
+ cleanupScreenSelector();
874
+ NSLog(@"🖥️ SCREEN SELECTION: Stopped");
875
+ return true;
876
+
877
+ } @catch (NSException *exception) {
878
+ NSLog(@"❌ Error stopping screen selection: %@", exception);
879
+ return false;
880
+ }
881
+ }
882
+
883
+ NSDictionary* getSelectedScreenInfo() {
884
+ if (!g_selectedScreenInfo) return nil;
885
+
886
+ NSDictionary *result = [g_selectedScreenInfo retain];
887
+ [g_selectedScreenInfo release];
888
+ g_selectedScreenInfo = nil;
889
+
890
+ return [result autorelease];
891
+ }
892
+
893
+ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
894
+ @try {
895
+ // Clean up any existing preview
896
+ cleanupRecordingPreview();
897
+
898
+ if (!screenInfo) return false;
899
+
900
+ // For screen recording preview, we show all OTHER screens as black overlay
901
+ // and keep the selected screen transparent
902
+ NSArray *screens = [NSScreen screens];
903
+ if (!screens || [screens count] == 0) return false;
904
+
905
+ int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
906
+
907
+ // Create overlay for each screen except the selected one
908
+ for (NSInteger i = 0; i < [screens count]; i++) {
909
+ if (i == selectedScreenId) continue; // Skip selected screen
910
+
911
+ NSScreen *screen = [screens objectAtIndex:i];
912
+ NSRect screenFrame = [screen frame];
913
+
914
+ // Create full-screen black overlay for non-selected screens
915
+ NSWindow *overlayWindow = [[NSWindow alloc] initWithContentRect:screenFrame
916
+ styleMask:NSWindowStyleMaskBorderless
917
+ backing:NSBackingStoreBuffered
918
+ defer:NO
919
+ screen:screen];
920
+
921
+ [overlayWindow setLevel:CGWindowLevelForKey(kCGOverlayWindowLevelKey)];
922
+ [overlayWindow setOpaque:NO];
923
+ [overlayWindow setBackgroundColor:[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5]];
924
+ [overlayWindow setIgnoresMouseEvents:YES];
925
+ [overlayWindow setAcceptsMouseMovedEvents:NO];
926
+ [overlayWindow setHasShadow:NO];
927
+ [overlayWindow setAlphaValue:1.0];
928
+ [overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
929
+
930
+ [overlayWindow orderFront:nil];
931
+ [overlayWindow makeKeyAndOrderFront:nil];
932
+
933
+ // Store for cleanup (reuse recording preview window variable)
934
+ if (!g_recordingPreviewWindow) {
935
+ g_recordingPreviewWindow = overlayWindow;
936
+ }
937
+ }
938
+
939
+ NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %d", selectedScreenId);
940
+
941
+ return true;
942
+
943
+ } @catch (NSException *exception) {
944
+ NSLog(@"❌ Error showing screen recording preview: %@", exception);
945
+ return false;
946
+ }
947
+ }
948
+
949
+ bool hideScreenRecordingPreview() {
950
+ return hideRecordingPreview(); // Reuse existing function
951
+ }
952
+
953
+ // NAPI Function: Start Window Selection
954
+ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
955
+ Napi::Env env = info.Env();
956
+
957
+ if (g_isWindowSelecting) {
958
+ Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
959
+ return env.Null();
960
+ }
961
+
962
+ @try {
963
+ // Get all windows
964
+ g_allWindows = [getAllSelectableWindows() retain];
965
+
966
+ if (!g_allWindows || [g_allWindows count] == 0) {
967
+ Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
968
+ return env.Null();
969
+ }
970
+
971
+ // Create overlay window (initially hidden)
972
+ NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
973
+ g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
974
+ styleMask:NSWindowStyleMaskBorderless
975
+ backing:NSBackingStoreBuffered
976
+ defer:NO];
977
+
978
+ [g_overlayWindow setLevel:CGWindowLevelForKey(kCGMaximumWindowLevelKey)]; // Absolute highest level
979
+ [g_overlayWindow setOpaque:NO];
980
+ [g_overlayWindow setBackgroundColor:[NSColor clearColor]];
981
+ [g_overlayWindow setIgnoresMouseEvents:NO];
982
+ [g_overlayWindow setAcceptsMouseMovedEvents:YES];
983
+ [g_overlayWindow setHasShadow:NO];
984
+ [g_overlayWindow setAlphaValue:1.0];
985
+ [g_overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorCanJoinAllSpaces];
986
+
987
+ // Create overlay view
988
+ g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
989
+ [g_overlayWindow setContentView:g_overlayView];
990
+
991
+ // Create select button with blue theme
992
+ g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 160, 60)];
993
+ [g_selectButton setTitle:@"Start Record"];
994
+ [g_selectButton setButtonType:NSButtonTypeMomentaryPushIn];
995
+ [g_selectButton setBezelStyle:NSBezelStyleRounded];
996
+ [g_selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
997
+
998
+ // Blue background with white text
999
+ [g_selectButton setWantsLayer:YES];
1000
+ [g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
1001
+ [g_selectButton.layer setCornerRadius:8.0];
1002
+ [g_selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
1003
+ [g_selectButton.layer setBorderWidth:2.0];
1004
+
1005
+ // White text color
1006
+ NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
1007
+ initWithString:[g_selectButton title]];
1008
+ [titleString addAttribute:NSForegroundColorAttributeName
1009
+ value:[NSColor whiteColor]
1010
+ range:NSMakeRange(0, [titleString length])];
1011
+ [g_selectButton setAttributedTitle:titleString];
1012
+
1013
+ // Add shadow for better visibility
1014
+ [g_selectButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
1015
+ [g_selectButton.layer setShadowOffset:NSMakeSize(0, -2)];
1016
+ [g_selectButton.layer setShadowRadius:4.0];
1017
+ [g_selectButton.layer setShadowOpacity:0.3];
1018
+
1019
+ // Create delegate for button action and timer
1020
+ g_delegate = [[WindowSelectorDelegate alloc] init];
1021
+ [g_selectButton setTarget:g_delegate];
1022
+ [g_selectButton setAction:@selector(selectButtonClicked:)];
1023
+
1024
+ // Add select button directly to window (not view) for proper layering
1025
+ [g_overlayWindow.contentView addSubview:g_selectButton];
1026
+
1027
+ // Create cancel button
1028
+ NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
1029
+ [cancelButton setTitle:@"Cancel"];
1030
+ [cancelButton setButtonType:NSButtonTypeMomentaryPushIn];
1031
+ [cancelButton setBezelStyle:NSBezelStyleRounded];
1032
+ [cancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightMedium]];
1033
+
1034
+ // Gray cancel button styling
1035
+ [cancelButton setWantsLayer:YES];
1036
+ [cancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.8] CGColor]];
1037
+ [cancelButton.layer setCornerRadius:6.0];
1038
+ [cancelButton.layer setBorderColor:[[NSColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor]];
1039
+ [cancelButton.layer setBorderWidth:1.0];
1040
+
1041
+ // White text for cancel button
1042
+ NSMutableAttributedString *cancelTitleString = [[NSMutableAttributedString alloc]
1043
+ initWithString:[cancelButton title]];
1044
+ [cancelTitleString addAttribute:NSForegroundColorAttributeName
1045
+ value:[NSColor whiteColor]
1046
+ range:NSMakeRange(0, [cancelTitleString length])];
1047
+ [cancelButton setAttributedTitle:cancelTitleString];
1048
+
1049
+ // Add shadow for cancel button
1050
+ [cancelButton.layer setShadowColor:[[NSColor blackColor] CGColor]];
1051
+ [cancelButton.layer setShadowOffset:NSMakeSize(0, -1)];
1052
+ [cancelButton.layer setShadowRadius:2.0];
1053
+ [cancelButton.layer setShadowOpacity:0.2];
1054
+
1055
+ [cancelButton setTarget:g_delegate];
1056
+ [cancelButton setAction:@selector(cancelButtonClicked:)];
1057
+
1058
+ // Add cancel button to window
1059
+ [g_overlayWindow.contentView addSubview:cancelButton];
1060
+
1061
+ // Cancel button reference will be found dynamically in positioning code
1062
+
1063
+ // Timer approach doesn't work well with Node.js
1064
+ // Instead, we'll use JavaScript polling via getWindowSelectionStatus
1065
+ // The JS side will call this function repeatedly to trigger overlay updates
1066
+ g_trackingTimer = nil; // No timer for now
1067
+
1068
+ // Add ESC key event monitor to cancel selection
1069
+ g_windowKeyEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown
1070
+ handler:^(NSEvent *event) {
1071
+ if ([event keyCode] == 53) { // ESC key
1072
+ NSLog(@"🪟 WINDOW SELECTION: ESC pressed - cancelling selection");
1073
+ cleanupWindowSelector();
1074
+ }
1075
+ }];
1076
+
1077
+ g_isWindowSelecting = true;
1078
+ g_selectedWindowInfo = nil;
1079
+
1080
+ return Napi::Boolean::New(env, true);
1081
+
1082
+ } @catch (NSException *exception) {
1083
+ cleanupWindowSelector();
1084
+ Napi::Error::New(env, [[exception reason] UTF8String]).ThrowAsJavaScriptException();
1085
+ return env.Null();
1086
+ }
1087
+ }
1088
+
1089
+ // NAPI Function: Stop Window Selection
1090
+ Napi::Value StopWindowSelection(const Napi::CallbackInfo& info) {
1091
+ Napi::Env env = info.Env();
1092
+
1093
+ if (!g_isWindowSelecting) {
1094
+ return Napi::Boolean::New(env, false);
1095
+ }
1096
+
1097
+ cleanupWindowSelector();
1098
+ return Napi::Boolean::New(env, true);
1099
+ }
1100
+
1101
+ // NAPI Function: Get Selected Window Info
1102
+ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1103
+ Napi::Env env = info.Env();
1104
+
1105
+ if (!g_selectedWindowInfo) {
1106
+ return env.Null();
1107
+ }
1108
+
1109
+ @try {
1110
+ Napi::Object result = Napi::Object::New(env);
1111
+ result.Set("id", Napi::Number::New(env, [[g_selectedWindowInfo objectForKey:@"id"] intValue]));
1112
+ result.Set("title", Napi::String::New(env, [[g_selectedWindowInfo objectForKey:@"title"] UTF8String]));
1113
+ result.Set("appName", Napi::String::New(env, [[g_selectedWindowInfo objectForKey:@"appName"] UTF8String]));
1114
+ // Original CGWindow coordinates
1115
+ result.Set("x", Napi::Number::New(env, [[g_selectedWindowInfo objectForKey:@"x"] intValue]));
1116
+ result.Set("y", Napi::Number::New(env, [[g_selectedWindowInfo objectForKey:@"y"] intValue]));
1117
+ result.Set("width", Napi::Number::New(env, [[g_selectedWindowInfo objectForKey:@"width"] intValue]));
1118
+ result.Set("height", Napi::Number::New(env, [[g_selectedWindowInfo objectForKey:@"height"] intValue]));
1119
+
1120
+ // Add overlay coordinates for direct use in recording
1121
+ // These are the exact coordinates used by the recording preview overlay
1122
+ int windowX = [[g_selectedWindowInfo objectForKey:@"x"] intValue];
1123
+ int windowY = [[g_selectedWindowInfo objectForKey:@"y"] intValue];
1124
+ int windowWidth = [[g_selectedWindowInfo objectForKey:@"width"] intValue];
1125
+ int windowHeight = [[g_selectedWindowInfo objectForKey:@"height"] intValue];
1126
+
1127
+ result.Set("overlayX", Napi::Number::New(env, windowX));
1128
+ result.Set("overlayY", Napi::Number::New(env, windowY));
1129
+ result.Set("overlayWidth", Napi::Number::New(env, windowWidth));
1130
+ result.Set("overlayHeight", Napi::Number::New(env, windowHeight));
1131
+
1132
+ // Determine which screen this window is on
1133
+ int x = [[g_selectedWindowInfo objectForKey:@"x"] intValue];
1134
+ int y = [[g_selectedWindowInfo objectForKey:@"y"] intValue];
1135
+ int width = [[g_selectedWindowInfo objectForKey:@"width"] intValue];
1136
+ int height = [[g_selectedWindowInfo objectForKey:@"height"] intValue];
1137
+
1138
+ NSLog(@"🎯 WINDOW SELECTED: %@ - \"%@\"",
1139
+ [g_selectedWindowInfo objectForKey:@"appName"],
1140
+ [g_selectedWindowInfo objectForKey:@"title"]);
1141
+ NSLog(@" 📊 Details: ID=%@, Pos=(%d,%d), Size=%dx%d",
1142
+ [g_selectedWindowInfo objectForKey:@"id"], x, y, width, height);
1143
+
1144
+ // Get all screens
1145
+ NSArray *screens = [NSScreen screens];
1146
+ NSScreen *windowScreen = nil;
1147
+ NSScreen *mainScreen = [NSScreen mainScreen];
1148
+
1149
+ for (NSScreen *screen in screens) {
1150
+ NSRect screenFrame = [screen frame];
1151
+
1152
+ // Convert window coordinates to screen-relative
1153
+ if (x >= screenFrame.origin.x &&
1154
+ x < screenFrame.origin.x + screenFrame.size.width &&
1155
+ y >= screenFrame.origin.y &&
1156
+ y < screenFrame.origin.y + screenFrame.size.height) {
1157
+ windowScreen = screen;
1158
+ break;
1159
+ }
1160
+ }
1161
+
1162
+ if (!windowScreen) {
1163
+ windowScreen = mainScreen;
1164
+ }
1165
+
1166
+ // Add screen information
1167
+ NSRect screenFrame = [windowScreen frame];
1168
+ result.Set("screenId", Napi::Number::New(env, [[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] ?
1169
+ [[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue] : 0));
1170
+ result.Set("screenX", Napi::Number::New(env, (int)screenFrame.origin.x));
1171
+ result.Set("screenY", Napi::Number::New(env, (int)screenFrame.origin.y));
1172
+ result.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
1173
+ result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1174
+
1175
+ // Clear selected window info after reading
1176
+ [g_selectedWindowInfo release];
1177
+ g_selectedWindowInfo = nil;
1178
+
1179
+ return result;
1180
+
1181
+ } @catch (NSException *exception) {
1182
+ return env.Null();
1183
+ }
1184
+ }
1185
+
1186
+ // NAPI Function: Bring Window To Front
1187
+ Napi::Value BringWindowToFront(const Napi::CallbackInfo& info) {
1188
+ Napi::Env env = info.Env();
1189
+
1190
+ if (info.Length() < 1) {
1191
+ Napi::TypeError::New(env, "Window ID required").ThrowAsJavaScriptException();
1192
+ return env.Null();
1193
+ }
1194
+
1195
+ int windowId = info[0].As<Napi::Number>().Int32Value();
1196
+
1197
+ @try {
1198
+ bool success = bringWindowToFront(windowId);
1199
+ return Napi::Boolean::New(env, success);
1200
+
1201
+ } @catch (NSException *exception) {
1202
+ return Napi::Boolean::New(env, false);
1203
+ }
1204
+ }
1205
+
1206
+ // NAPI Function: Enable/Disable Auto Bring To Front
1207
+ Napi::Value SetBringToFrontEnabled(const Napi::CallbackInfo& info) {
1208
+ Napi::Env env = info.Env();
1209
+
1210
+ if (info.Length() < 1) {
1211
+ Napi::TypeError::New(env, "Boolean value required").ThrowAsJavaScriptException();
1212
+ return env.Null();
1213
+ }
1214
+
1215
+ bool enabled = info[0].As<Napi::Boolean>();
1216
+ g_bringToFrontEnabled = enabled;
1217
+
1218
+ NSLog(@"🔄 Auto bring-to-front: %s", enabled ? "ENABLED" : "DISABLED");
1219
+
1220
+ return Napi::Boolean::New(env, true);
1221
+ }
1222
+
1223
+ // NAPI Function: Get Window Selection Status
1224
+ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
1225
+ Napi::Env env = info.Env();
1226
+
1227
+ // Update overlay each time status is requested (JavaScript polling approach)
1228
+ if (g_isWindowSelecting) {
1229
+ updateOverlay();
1230
+ }
1231
+
1232
+ Napi::Object result = Napi::Object::New(env);
1233
+ result.Set("isSelecting", Napi::Boolean::New(env, g_isWindowSelecting));
1234
+ result.Set("hasSelectedWindow", Napi::Boolean::New(env, g_selectedWindowInfo != nil));
1235
+ result.Set("windowCount", Napi::Number::New(env, g_allWindows ? [g_allWindows count] : 0));
1236
+ result.Set("hasOverlay", Napi::Boolean::New(env, g_overlayWindow != nil));
1237
+
1238
+ if (g_currentWindowUnderCursor) {
1239
+ Napi::Object currentWindow = Napi::Object::New(env);
1240
+ currentWindow.Set("id", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"id"] intValue]));
1241
+ currentWindow.Set("title", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"title"] UTF8String]));
1242
+ currentWindow.Set("appName", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"appName"] UTF8String]));
1243
+ currentWindow.Set("x", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"x"] intValue]));
1244
+ currentWindow.Set("y", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"y"] intValue]));
1245
+ currentWindow.Set("width", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"width"] intValue]));
1246
+ currentWindow.Set("height", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"height"] intValue]));
1247
+ result.Set("currentWindow", currentWindow);
1248
+ }
1249
+
1250
+ return result;
1251
+ }
1252
+
1253
+ // NAPI Function: Show Recording Preview
1254
+ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1255
+ Napi::Env env = info.Env();
1256
+
1257
+ if (info.Length() < 1) {
1258
+ Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1259
+ return env.Null();
1260
+ }
1261
+
1262
+ if (!info[0].IsObject()) {
1263
+ Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1264
+ return env.Null();
1265
+ }
1266
+
1267
+ @try {
1268
+ Napi::Object windowInfoObj = info[0].As<Napi::Object>();
1269
+
1270
+ // Convert NAPI object to NSDictionary
1271
+ NSMutableDictionary *windowInfo = [[NSMutableDictionary alloc] init];
1272
+
1273
+ if (windowInfoObj.Has("id")) {
1274
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("id").As<Napi::Number>().Int32Value()] forKey:@"id"];
1275
+ }
1276
+ if (windowInfoObj.Has("title")) {
1277
+ [windowInfo setObject:[NSString stringWithUTF8String:windowInfoObj.Get("title").As<Napi::String>().Utf8Value().c_str()] forKey:@"title"];
1278
+ }
1279
+ if (windowInfoObj.Has("appName")) {
1280
+ [windowInfo setObject:[NSString stringWithUTF8String:windowInfoObj.Get("appName").As<Napi::String>().Utf8Value().c_str()] forKey:@"appName"];
1281
+ }
1282
+ if (windowInfoObj.Has("x")) {
1283
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("x").As<Napi::Number>().Int32Value()] forKey:@"x"];
1284
+ }
1285
+ if (windowInfoObj.Has("y")) {
1286
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("y").As<Napi::Number>().Int32Value()] forKey:@"y"];
1287
+ }
1288
+ if (windowInfoObj.Has("width")) {
1289
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("width").As<Napi::Number>().Int32Value()] forKey:@"width"];
1290
+ }
1291
+ if (windowInfoObj.Has("height")) {
1292
+ [windowInfo setObject:[NSNumber numberWithInt:windowInfoObj.Get("height").As<Napi::Number>().Int32Value()] forKey:@"height"];
1293
+ }
1294
+
1295
+ bool success = showRecordingPreview(windowInfo);
1296
+ [windowInfo release];
1297
+
1298
+ return Napi::Boolean::New(env, success);
1299
+
1300
+ } @catch (NSException *exception) {
1301
+ return Napi::Boolean::New(env, false);
1302
+ }
1303
+ }
1304
+
1305
+ // NAPI Function: Hide Recording Preview
1306
+ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
1307
+ Napi::Env env = info.Env();
1308
+
1309
+ @try {
1310
+ bool success = hideRecordingPreview();
1311
+ return Napi::Boolean::New(env, success);
1312
+
1313
+ } @catch (NSException *exception) {
1314
+ return Napi::Boolean::New(env, false);
1315
+ }
1316
+ }
1317
+
1318
+ // NAPI Function: Start Screen Selection
1319
+ Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
1320
+ Napi::Env env = info.Env();
1321
+
1322
+ @try {
1323
+ bool success = startScreenSelection();
1324
+ return Napi::Boolean::New(env, success);
1325
+
1326
+ } @catch (NSException *exception) {
1327
+ return Napi::Boolean::New(env, false);
1328
+ }
1329
+ }
1330
+
1331
+ // NAPI Function: Stop Screen Selection
1332
+ Napi::Value StopScreenSelection(const Napi::CallbackInfo& info) {
1333
+ Napi::Env env = info.Env();
1334
+
1335
+ @try {
1336
+ bool success = stopScreenSelection();
1337
+ return Napi::Boolean::New(env, success);
1338
+
1339
+ } @catch (NSException *exception) {
1340
+ return Napi::Boolean::New(env, false);
1341
+ }
1342
+ }
1343
+
1344
+ // NAPI Function: Get Selected Screen Info
1345
+ Napi::Value GetSelectedScreenInfo(const Napi::CallbackInfo& info) {
1346
+ Napi::Env env = info.Env();
1347
+
1348
+ @try {
1349
+ NSDictionary *screenInfo = getSelectedScreenInfo();
1350
+ if (!screenInfo) {
1351
+ return env.Null();
1352
+ }
1353
+
1354
+ Napi::Object result = Napi::Object::New(env);
1355
+ result.Set("id", Napi::Number::New(env, [[screenInfo objectForKey:@"id"] intValue]));
1356
+ result.Set("name", Napi::String::New(env, [[screenInfo objectForKey:@"name"] UTF8String]));
1357
+ result.Set("x", Napi::Number::New(env, [[screenInfo objectForKey:@"x"] intValue]));
1358
+ result.Set("y", Napi::Number::New(env, [[screenInfo objectForKey:@"y"] intValue]));
1359
+ result.Set("width", Napi::Number::New(env, [[screenInfo objectForKey:@"width"] intValue]));
1360
+ result.Set("height", Napi::Number::New(env, [[screenInfo objectForKey:@"height"] intValue]));
1361
+ result.Set("resolution", Napi::String::New(env, [[screenInfo objectForKey:@"resolution"] UTF8String]));
1362
+ result.Set("isPrimary", Napi::Boolean::New(env, [[screenInfo objectForKey:@"isPrimary"] boolValue]));
1363
+
1364
+ NSLog(@"🖥️ SCREEN SELECTED: %@ (%@)",
1365
+ [screenInfo objectForKey:@"name"],
1366
+ [screenInfo objectForKey:@"resolution"]);
1367
+
1368
+ return result;
1369
+
1370
+ } @catch (NSException *exception) {
1371
+ return env.Null();
1372
+ }
1373
+ }
1374
+
1375
+ // NAPI Function: Show Screen Recording Preview
1376
+ Napi::Value ShowScreenRecordingPreview(const Napi::CallbackInfo& info) {
1377
+ Napi::Env env = info.Env();
1378
+
1379
+ if (info.Length() < 1) {
1380
+ Napi::TypeError::New(env, "Screen info object required").ThrowAsJavaScriptException();
1381
+ return env.Null();
1382
+ }
1383
+
1384
+ if (!info[0].IsObject()) {
1385
+ Napi::TypeError::New(env, "Screen info must be an object").ThrowAsJavaScriptException();
1386
+ return env.Null();
1387
+ }
1388
+
1389
+ @try {
1390
+ Napi::Object screenInfoObj = info[0].As<Napi::Object>();
1391
+
1392
+ // Convert NAPI object to NSDictionary
1393
+ NSMutableDictionary *screenInfo = [[NSMutableDictionary alloc] init];
1394
+
1395
+ if (screenInfoObj.Has("id")) {
1396
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("id").As<Napi::Number>().Int32Value()] forKey:@"id"];
1397
+ }
1398
+ if (screenInfoObj.Has("name")) {
1399
+ [screenInfo setObject:[NSString stringWithUTF8String:screenInfoObj.Get("name").As<Napi::String>().Utf8Value().c_str()] forKey:@"name"];
1400
+ }
1401
+ if (screenInfoObj.Has("resolution")) {
1402
+ [screenInfo setObject:[NSString stringWithUTF8String:screenInfoObj.Get("resolution").As<Napi::String>().Utf8Value().c_str()] forKey:@"resolution"];
1403
+ }
1404
+ if (screenInfoObj.Has("x")) {
1405
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("x").As<Napi::Number>().Int32Value()] forKey:@"x"];
1406
+ }
1407
+ if (screenInfoObj.Has("y")) {
1408
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("y").As<Napi::Number>().Int32Value()] forKey:@"y"];
1409
+ }
1410
+ if (screenInfoObj.Has("width")) {
1411
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("width").As<Napi::Number>().Int32Value()] forKey:@"width"];
1412
+ }
1413
+ if (screenInfoObj.Has("height")) {
1414
+ [screenInfo setObject:[NSNumber numberWithInt:screenInfoObj.Get("height").As<Napi::Number>().Int32Value()] forKey:@"height"];
1415
+ }
1416
+
1417
+ bool success = showScreenRecordingPreview(screenInfo);
1418
+ [screenInfo release];
1419
+
1420
+ return Napi::Boolean::New(env, success);
1421
+
1422
+ } @catch (NSException *exception) {
1423
+ return Napi::Boolean::New(env, false);
1424
+ }
1425
+ }
1426
+
1427
+ // NAPI Function: Hide Screen Recording Preview
1428
+ Napi::Value HideScreenRecordingPreview(const Napi::CallbackInfo& info) {
1429
+ Napi::Env env = info.Env();
1430
+
1431
+ @try {
1432
+ bool success = hideScreenRecordingPreview();
1433
+ return Napi::Boolean::New(env, success);
1434
+
1435
+ } @catch (NSException *exception) {
1436
+ return Napi::Boolean::New(env, false);
1437
+ }
1438
+ }
1439
+
1440
+ // Export functions
1441
+ Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
1442
+ exports.Set("startWindowSelection", Napi::Function::New(env, StartWindowSelection));
1443
+ exports.Set("stopWindowSelection", Napi::Function::New(env, StopWindowSelection));
1444
+ exports.Set("getSelectedWindowInfo", Napi::Function::New(env, GetSelectedWindowInfo));
1445
+ exports.Set("getWindowSelectionStatus", Napi::Function::New(env, GetWindowSelectionStatus));
1446
+ exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
1447
+ exports.Set("setBringToFrontEnabled", Napi::Function::New(env, SetBringToFrontEnabled));
1448
+ exports.Set("showRecordingPreview", Napi::Function::New(env, ShowRecordingPreview));
1449
+ exports.Set("hideRecordingPreview", Napi::Function::New(env, HideRecordingPreview));
1450
+ exports.Set("startScreenSelection", Napi::Function::New(env, StartScreenSelection));
1451
+ exports.Set("stopScreenSelection", Napi::Function::New(env, StopScreenSelection));
1452
+ exports.Set("getSelectedScreenInfo", Napi::Function::New(env, GetSelectedScreenInfo));
1453
+ exports.Set("showScreenRecordingPreview", Napi::Function::New(env, ShowScreenRecordingPreview));
1454
+ exports.Set("hideScreenRecordingPreview", Napi::Function::New(env, HideScreenRecordingPreview));
1455
+
1456
+ return exports;
1457
+ }