node-mac-recorder 2.9.2 → 2.10.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.
@@ -7,18 +7,13 @@
7
7
  #import <Accessibility/Accessibility.h>
8
8
  #import <QuartzCore/QuartzCore.h>
9
9
 
10
- // Forward declarations
11
- @class WindowSelectorOverlayView;
12
-
13
10
  // Global state for window selection
14
11
  static bool g_isWindowSelecting = false;
15
12
  static NSWindow *g_overlayWindow = nil;
16
13
  static NSView *g_overlayView = nil;
17
- static WindowSelectorOverlayView *g_selectedOverlayView = nil; // Track selected overlay
18
14
  static NSButton *g_selectButton = nil;
19
15
  static NSTimer *g_trackingTimer = nil;
20
- static NSDictionary *g_selectedWindowInfo = nil; // Window info for recording
21
- static NSDictionary *g_clickedWindowInfo = nil; // Separate state for clicked selection (NOT for recording)
16
+ static NSDictionary *g_selectedWindowInfo = nil;
22
17
  static NSMutableArray *g_allWindows = nil;
23
18
  static NSDictionary *g_currentWindowUnderCursor = nil;
24
19
  static bool g_bringToFrontEnabled = true; // Default enabled
@@ -59,7 +54,6 @@ void updateScreenOverlays();
59
54
  @interface WindowSelectorOverlayView : NSView
60
55
  @property (nonatomic, strong) NSDictionary *windowInfo;
61
56
  @property (nonatomic) BOOL isActiveWindow;
62
- @property (nonatomic) BOOL isSelectedWindow;
63
57
  @end
64
58
 
65
59
  @implementation WindowSelectorOverlayView
@@ -69,10 +63,9 @@ void updateScreenOverlays();
69
63
  if (self) {
70
64
  // Use layer for background instead of custom drawing
71
65
  self.wantsLayer = YES;
72
- self.isActiveWindow = NO; // Default to inactive (will be set by updateOverlay)
73
- self.isSelectedWindow = NO; // Default to not selected
66
+ self.isActiveWindow = YES; // Default to active for current window under mouse
74
67
 
75
- // Set initial appearance
68
+ // Set purple background with border using layer
76
69
  [self updateAppearance];
77
70
 
78
71
  // Window selector overlay view created
@@ -81,27 +74,19 @@ void updateScreenOverlays();
81
74
  }
82
75
 
83
76
  - (void)updateAppearance {
84
- if (self.isSelectedWindow) {
85
- // Selected window: same background but medium border (2px)
86
- self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor]; // Same as highlight
87
- self.layer.borderColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:1.0] CGColor];
88
- self.layer.borderWidth = 2.0; // Medium border for selected window
89
- // Selected window appearance set
90
- } else if (self.isActiveWindow) {
91
- // Active window (highlighted): thin border (1px)
92
- self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor];
93
- self.layer.borderColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.8] CGColor];
94
- self.layer.borderWidth = 1.0; // Thin border for highlighted window
95
- // Active window appearance set
96
- } else {
97
- // Inactive window: no border
98
- self.layer.backgroundColor = [[NSColor colorWithRed:0.4 green:0.2 blue:0.6 alpha:0.25] CGColor];
99
- self.layer.borderColor = [[NSColor clearColor] CGColor];
100
- self.layer.borderWidth = 0.0; // No border for inactive window
101
- // Inactive window appearance set
102
- }
77
+ if (self.isActiveWindow) {
78
+ // Active window: brighter background
79
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor];
80
+ // Active window appearance set
81
+ } else {
82
+ // Inactive window: dimmer background
83
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.4 green:0.2 blue:0.6 alpha:0.25] CGColor];
84
+ // Inactive window appearance set
85
+ }
103
86
 
104
- // Common styling
87
+ // No border to match screen selector
88
+ self.layer.borderColor = [[NSColor clearColor] CGColor];
89
+ self.layer.borderWidth = 0.0;
105
90
  self.layer.cornerRadius = 8.0;
106
91
  self.layer.masksToBounds = YES;
107
92
  self.layer.shadowOpacity = 0.0;
@@ -116,71 +101,6 @@ void updateScreenOverlays();
116
101
  }
117
102
  }
118
103
 
119
- - (void)setIsSelectedWindow:(BOOL)isSelectedWindow {
120
- if (_isSelectedWindow != isSelectedWindow) {
121
- _isSelectedWindow = isSelectedWindow;
122
- [self updateAppearance];
123
- }
124
- }
125
-
126
- - (void)mouseDown:(NSEvent *)event {
127
- // Handle mouse click to toggle window selection (ONLY selection, no recording start)
128
- if (self.windowInfo) {
129
- // Check if this window is already selected
130
- BOOL wasSelected = (g_clickedWindowInfo &&
131
- [g_clickedWindowInfo isEqualToDictionary:self.windowInfo]);
132
-
133
- if (wasSelected) {
134
- // Deselect this window - return to normal highlight behavior
135
- self.isSelectedWindow = NO;
136
-
137
- // Clear global clicked selection (NOT recording selection)
138
- if (g_clickedWindowInfo) {
139
- [g_clickedWindowInfo release];
140
- g_clickedWindowInfo = nil;
141
- }
142
- g_selectedOverlayView = nil;
143
-
144
- NSLog(@"🚫 WINDOW DESELECTED - Back to normal highlight mode: %@ - \"%@\"",
145
- [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
146
- [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
147
- } else {
148
- // Deselect previous window if any
149
- if (g_selectedOverlayView && g_selectedOverlayView != self) {
150
- g_selectedOverlayView.isSelectedWindow = NO;
151
- [g_selectedOverlayView updateAppearance];
152
- }
153
-
154
- // Select this window (for visual selection only, NOT for recording)
155
- self.isSelectedWindow = YES;
156
-
157
- // Update global clicked window info (separate from recording selection)
158
- if (g_clickedWindowInfo) {
159
- [g_clickedWindowInfo release];
160
- }
161
- g_clickedWindowInfo = [self.windowInfo retain];
162
-
163
- // Update global selected overlay reference
164
- g_selectedOverlayView = self;
165
-
166
- NSLog(@"🎯 WINDOW CLICKED - Visual selection only (NOT recording): %@ - \"%@\"",
167
- [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
168
- [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
169
- }
170
-
171
- // Update appearance for this overlay
172
- [self updateAppearance];
173
-
174
- // DON'T start recording here - that's only for the "Start Record" button
175
- // Consume the event completely to prevent any propagation
176
- NSLog(@"🛑 MOUSE EVENT CONSUMED - No recording started");
177
-
178
- // IMPORTANT: Return YES to indicate we handled the event
179
- // This prevents the event from propagating to other handlers
180
- return;
181
- }
182
- }
183
-
184
104
  // Layer-based approach, no custom drawing needed
185
105
 
186
106
  @end
@@ -299,19 +219,9 @@ void updateScreenOverlays();
299
219
 
300
220
  @implementation WindowSelectorDelegate
301
221
  - (void)selectButtonClicked:(id)sender {
302
- if (g_clickedWindowInfo) {
303
- // Copy clicked window info to recording selection
304
- if (g_selectedWindowInfo) {
305
- [g_selectedWindowInfo release];
306
- }
307
- g_selectedWindowInfo = [g_clickedWindowInfo retain];
308
-
309
- NSLog(@"🎯 START RECORDING: Clicked window confirmed for recording - %@ - \"%@\"",
310
- [g_selectedWindowInfo objectForKey:@"appName"] ?: @"Unknown",
311
- [g_selectedWindowInfo objectForKey:@"title"] ?: @"Untitled");
222
+ if (g_currentWindowUnderCursor) {
223
+ g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
312
224
  cleanupWindowSelector();
313
- } else {
314
- NSLog(@"⚠️ No window clicked - cannot start recording");
315
225
  }
316
226
  }
317
227
 
@@ -534,294 +444,271 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
534
444
  }
535
445
  }
536
446
 
537
- // Update overlay to show window under cursor with highlighting (but no auto-bring-to-front)
447
+ // Update overlay to highlight window under cursor
538
448
  void updateOverlay() {
539
449
  @autoreleasepool {
540
450
  if (!g_isWindowSelecting || !g_overlayWindow) return;
541
451
 
542
- // Get current cursor position for highlighting
452
+ // Get current cursor position
543
453
  NSPoint mouseLocation = [NSEvent mouseLocation];
454
+ // Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
544
455
  NSScreen *mainScreen = [NSScreen mainScreen];
545
456
  CGFloat screenHeight = [mainScreen frame].size.height;
546
457
  CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
547
458
 
548
- // Find window under cursor for highlighting
459
+ // Find window under cursor
549
460
  NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
550
461
 
551
- // Update current window under cursor for highlighting
552
462
  if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
463
+ // Update current window
553
464
  [g_currentWindowUnderCursor release];
554
465
  g_currentWindowUnderCursor = [windowUnderCursor retain];
555
- }
556
-
557
- // If no window under cursor, show first available window
558
- if (!windowUnderCursor) {
559
- if (g_allWindows && [g_allWindows count] > 0) {
560
- windowUnderCursor = [g_allWindows objectAtIndex:0];
561
- } else {
562
- return; // No windows available
466
+
467
+ // Update overlay position and size
468
+ int x = [[windowUnderCursor objectForKey:@"x"] intValue];
469
+ int y = [[windowUnderCursor objectForKey:@"y"] intValue];
470
+ int width = [[windowUnderCursor objectForKey:@"width"] intValue];
471
+ int height = [[windowUnderCursor objectForKey:@"height"] intValue];
472
+
473
+ // Find which screen contains the window center
474
+ NSArray *screens = [NSScreen screens];
475
+ NSScreen *windowScreen = nil;
476
+ CGFloat windowCenterX = x + width / 2;
477
+ CGFloat windowCenterY = y + height / 2;
478
+
479
+ for (NSScreen *screen in screens) {
480
+ NSRect screenFrame = [screen frame];
481
+ // Convert screen frame to CGWindow coordinates
482
+ CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
483
+ CGFloat screenBottom = screenFrame.origin.y;
484
+ CGFloat screenLeft = screenFrame.origin.x;
485
+ CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
486
+
487
+ if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
488
+ windowCenterY >= screenBottom && windowCenterY <= screenTop) {
489
+ windowScreen = screen;
490
+ break;
491
+ }
563
492
  }
564
- }
565
-
566
- // Update overlay position and size
567
- int x = [[windowUnderCursor objectForKey:@"x"] intValue];
568
- int y = [[windowUnderCursor objectForKey:@"y"] intValue];
569
- int width = [[windowUnderCursor objectForKey:@"width"] intValue];
570
- int height = [[windowUnderCursor objectForKey:@"height"] intValue];
571
-
572
- // Find which screen contains the window center
573
- NSArray *screens = [NSScreen screens];
574
- NSScreen *windowScreen = nil;
575
- CGFloat windowCenterX = x + width / 2;
576
- CGFloat windowCenterY = y + height / 2;
577
-
578
- for (NSScreen *screen in screens) {
579
- NSRect screenFrame = [screen frame];
580
- // Convert screen frame to CGWindow coordinates
581
- CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
582
- CGFloat screenBottom = screenFrame.origin.y;
583
- CGFloat screenLeft = screenFrame.origin.x;
584
- CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
585
-
586
- if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
587
- windowCenterY >= screenBottom && windowCenterY <= screenTop) {
588
- windowScreen = screen;
589
- break;
493
+
494
+ // Use main screen if no specific screen found
495
+ if (!windowScreen) windowScreen = [NSScreen mainScreen];
496
+
497
+ // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
498
+ NSRect screenFrame = [windowScreen frame];
499
+ CGFloat screenHeight = screenFrame.size.height;
500
+ CGFloat adjustedY = screenHeight - y - height;
501
+
502
+ // Window coordinates are in global space, overlay frame should be screen-relative
503
+ // Keep X coordinate as-is (already in global space which is what we want)
504
+ // Only convert Y from top-left to bottom-left coordinate system
505
+ NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
506
+
507
+ NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
508
+ NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
509
+
510
+ NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
511
+ NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
512
+ NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
513
+ overlayFrame.origin.x, overlayFrame.origin.y,
514
+ overlayFrame.size.width, overlayFrame.size.height,
515
+ [g_overlayWindow level]);
516
+
517
+ // Bring window to front if enabled
518
+ if (g_bringToFrontEnabled) {
519
+ int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
520
+ if (windowId > 0) {
521
+ bool success = bringWindowToFront(windowId);
522
+ if (!success) {
523
+ NSLog(@" ⚠️ Failed to bring window to front");
524
+ }
525
+ }
590
526
  }
591
- }
592
-
593
- // Use main screen if no specific screen found
594
- if (!windowScreen) windowScreen = [NSScreen mainScreen];
595
-
596
- // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
597
- NSRect screenFrame = [windowScreen frame];
598
- CGFloat windowScreenHeight = screenFrame.size.height;
599
- CGFloat adjustedY = windowScreenHeight - y - height;
600
-
601
- // Window coordinates are in global space, overlay frame should be screen-relative
602
- // Keep X coordinate as-is (already in global space which is what we want)
603
- // Only convert Y from top-left to bottom-left coordinate system
604
- NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
605
-
606
- NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
607
- NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
608
-
609
- NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
610
- NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
611
- NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
612
- overlayFrame.origin.x, overlayFrame.origin.y,
613
- overlayFrame.size.width, overlayFrame.size.height,
614
- [g_overlayWindow level]);
615
-
616
- // Bring window to front if enabled
617
- if (g_bringToFrontEnabled) {
618
- int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
619
- if (windowId > 0) {
620
- bool success = bringWindowToFront(windowId);
621
- if (!success) {
622
- NSLog(@" ⚠️ Failed to bring window to front");
527
+
528
+ // Ensure overlay is on the correct screen
529
+ [g_overlayWindow setFrame:overlayFrame display:YES];
530
+
531
+ // Update overlay view window info
532
+ [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
533
+
534
+ // Add/update info label above button
535
+ NSTextField *infoLabel = nil;
536
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
537
+ if ([subview isKindOfClass:[NSTextField class]]) {
538
+ infoLabel = (NSTextField*)subview;
539
+ break;
623
540
  }
624
541
  }
625
- }
626
-
627
- // Ensure overlay is on the correct screen
628
- [g_overlayWindow setFrame:overlayFrame display:YES];
629
-
630
- // Update overlay view window info
631
- [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
632
-
633
- // Don't automatically mark as selected - wait for user click
634
- [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
635
- [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
636
-
637
- // Add/update info label above button
638
- NSTextField *infoLabel = nil;
639
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
640
- if ([subview isKindOfClass:[NSTextField class]]) {
641
- infoLabel = (NSTextField*)subview;
642
- break;
542
+
543
+ if (!infoLabel) {
544
+ infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
545
+ [infoLabel setEditable:NO];
546
+ [infoLabel setSelectable:NO];
547
+ [infoLabel setBezeled:NO];
548
+ [infoLabel setDrawsBackground:NO];
549
+ [infoLabel setAlignment:NSTextAlignmentCenter];
550
+ [infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
551
+ [infoLabel setTextColor:[NSColor whiteColor]];
552
+
553
+ // Force no borders on info label
554
+ [infoLabel setWantsLayer:YES];
555
+ infoLabel.layer.borderWidth = 0.0;
556
+ infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
557
+ infoLabel.layer.cornerRadius = 0.0;
558
+ infoLabel.layer.masksToBounds = YES;
559
+
560
+ [g_overlayWindow.contentView addSubview:infoLabel];
643
561
  }
644
- }
645
-
646
- if (!infoLabel) {
647
- infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
648
- [infoLabel setEditable:NO];
649
- [infoLabel setSelectable:NO];
650
- [infoLabel setBezeled:NO];
651
- [infoLabel setDrawsBackground:NO];
652
- [infoLabel setAlignment:NSTextAlignmentCenter];
653
- [infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
654
- [infoLabel setTextColor:[NSColor whiteColor]];
655
-
656
- // Force no borders on info label
657
- [infoLabel setWantsLayer:YES];
658
- infoLabel.layer.borderWidth = 0.0;
659
- infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
660
- infoLabel.layer.cornerRadius = 0.0;
661
- infoLabel.layer.masksToBounds = YES;
662
-
663
- [g_overlayWindow.contentView addSubview:infoLabel];
664
- }
665
-
666
- // Add/update app icon
667
- NSImageView *appIconView = nil;
668
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
669
- if ([subview isKindOfClass:[NSImageView class]]) {
670
- appIconView = (NSImageView*)subview;
671
- break;
562
+
563
+ // Add/update app icon
564
+ NSImageView *appIconView = nil;
565
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
566
+ if ([subview isKindOfClass:[NSImageView class]]) {
567
+ appIconView = (NSImageView*)subview;
568
+ break;
569
+ }
672
570
  }
673
- }
674
-
675
- if (!appIconView) {
676
- appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
677
- [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
678
- [appIconView setWantsLayer:YES];
679
- [appIconView.layer setCornerRadius:8.0];
680
- [appIconView.layer setMasksToBounds:YES];
681
- [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
682
-
683
- // Force no borders on app icon view
684
- appIconView.layer.borderWidth = 0.0;
685
- appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
686
- appIconView.layer.shadowOpacity = 0.0;
687
- appIconView.layer.shadowRadius = 0.0;
688
- appIconView.layer.shadowOffset = NSMakeSize(0, 0);
689
-
690
- [g_overlayWindow.contentView addSubview:appIconView];
691
- NSLog(@"🖼️ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
692
- appIconView.frame.origin.x, appIconView.frame.origin.y,
693
- appIconView.frame.size.width, appIconView.frame.size.height);
694
- }
695
-
696
- // Get app icon using NSWorkspace
697
- NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
698
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
699
- NSArray *runningApps = [workspace runningApplications];
700
- NSImage *appIcon = nil;
701
-
702
- for (NSRunningApplication *app in runningApps) {
703
- if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
704
- appIcon = [app icon];
705
- break;
571
+
572
+ if (!appIconView) {
573
+ appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
574
+ [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
575
+ [appIconView setWantsLayer:YES];
576
+ [appIconView.layer setCornerRadius:8.0];
577
+ [appIconView.layer setMasksToBounds:YES];
578
+ [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
579
+
580
+ // Force no borders on app icon view
581
+ appIconView.layer.borderWidth = 0.0;
582
+ appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
583
+ appIconView.layer.shadowOpacity = 0.0;
584
+ appIconView.layer.shadowRadius = 0.0;
585
+ appIconView.layer.shadowOffset = NSMakeSize(0, 0);
586
+
587
+ [g_overlayWindow.contentView addSubview:appIconView];
588
+ NSLog(@"🖼️ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
589
+ appIconView.frame.origin.x, appIconView.frame.origin.y,
590
+ appIconView.frame.size.width, appIconView.frame.size.height);
706
591
  }
707
- }
708
-
709
- // Fallback to generic app icon if not found
710
- if (!appIcon) {
711
- appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
712
- NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
713
- } else {
714
- NSLog(@"✅ Found app icon for: %@", iconAppName);
715
- }
716
-
717
- [appIconView setImage:appIcon];
718
- NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
719
-
720
- // Update label text
721
- NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
722
- NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
723
- [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
724
-
725
- // Position buttons - Start Record in center, Cancel below it
726
- if (g_selectButton) {
727
- NSSize buttonSize = [g_selectButton frame].size;
728
- NSPoint buttonCenter = NSMakePoint(
729
- (width - buttonSize.width) / 2,
730
- (height - buttonSize.height) / 2 + 15 // Slightly above center
731
- );
732
- [g_selectButton setFrameOrigin:buttonCenter];
733
592
 
734
- // Position app icon above text label
735
- NSPoint iconCenter = NSMakePoint(
736
- (width - 96) / 2, // Center horizontally (icon is 96px wide)
737
- buttonCenter.y + buttonSize.height + 60 + 10 // Above label + text height + margin
738
- );
739
- [appIconView setFrameOrigin:iconCenter];
740
- NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
741
- iconCenter.x, iconCenter.y, (float)width, (float)height);
742
-
743
- // Add fast horizontal floating animation after positioning
744
- [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
745
- [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
746
-
747
- // Faster horizontal float animation only
748
- CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
749
- floatAnimationX.fromValue = @(-4.0);
750
- floatAnimationX.toValue = @(4.0);
751
- floatAnimationX.duration = 1.0; // Much faster animation
752
- floatAnimationX.repeatCount = HUGE_VALF;
753
- floatAnimationX.autoreverses = YES;
754
- floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
755
- [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
756
-
757
- // Position info label at overlay center, above button
758
- NSPoint labelCenter = NSMakePoint(
759
- (width - [infoLabel frame].size.width) / 2, // Center horizontally
760
- buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
761
- );
762
- [infoLabel setFrameOrigin:labelCenter];
593
+ // Get app icon using NSWorkspace
594
+ NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
595
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
596
+ NSArray *runningApps = [workspace runningApplications];
597
+ NSImage *appIcon = nil;
763
598
 
764
- // Position cancel button below the main button
765
- NSButton *cancelButton = nil;
766
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
767
- if ([subview isKindOfClass:[NSButton class]] &&
768
- [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
769
- cancelButton = (NSButton*)subview;
599
+ for (NSRunningApplication *app in runningApps) {
600
+ if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
601
+ appIcon = [app icon];
770
602
  break;
771
603
  }
772
604
  }
773
605
 
774
- if (cancelButton) {
775
- NSSize cancelButtonSize = [cancelButton frame].size;
776
- NSPoint cancelButtonCenter = NSMakePoint(
777
- (width - cancelButtonSize.width) / 2,
778
- buttonCenter.y - buttonSize.height - 20 // 20px below main button
779
- );
780
- [cancelButton setFrameOrigin:cancelButtonCenter];
606
+ // Fallback to generic app icon if not found
607
+ if (!appIcon) {
608
+ appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
609
+ NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
610
+ } else {
611
+ NSLog(@"✅ Found app icon for: %@", iconAppName);
781
612
  }
782
- }
783
-
784
- // Show overlay for the current window (will be selected when clicked)
785
- [g_overlayWindow orderFront:nil];
786
- [g_overlayWindow makeKeyAndOrderFront:nil];
787
-
788
- // Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
789
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
790
- if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
791
- [subview setWantsLayer:YES];
792
- if (subview.layer) {
793
- subview.layer.borderWidth = 0.0;
794
- subview.layer.borderColor = [[NSColor clearColor] CGColor];
795
- subview.layer.masksToBounds = YES;
796
- subview.layer.shadowOpacity = 0.0;
797
- subview.layer.shadowRadius = 0.0;
798
- subview.layer.shadowOffset = NSMakeSize(0, 0);
613
+
614
+ [appIconView setImage:appIcon];
615
+ NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
616
+
617
+ // Update label text
618
+ NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
619
+ NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
620
+ [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
621
+
622
+ // Position buttons - Start Record in center, Cancel below it
623
+ if (g_selectButton) {
624
+ NSSize buttonSize = [g_selectButton frame].size;
625
+ NSPoint buttonCenter = NSMakePoint(
626
+ (width - buttonSize.width) / 2,
627
+ (height - buttonSize.height) / 2 + 15 // Slightly above center
628
+ );
629
+ [g_selectButton setFrameOrigin:buttonCenter];
630
+
631
+ // Position app icon above text label
632
+ NSPoint iconCenter = NSMakePoint(
633
+ (width - 96) / 2, // Center horizontally (icon is 96px wide)
634
+ buttonCenter.y + buttonSize.height + 60 + 10 // Above label + text height + margin
635
+ );
636
+ [appIconView setFrameOrigin:iconCenter];
637
+ NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
638
+ iconCenter.x, iconCenter.y, (float)width, (float)height);
639
+
640
+ // Add fast horizontal floating animation after positioning
641
+ [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
642
+ [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
643
+
644
+ // Faster horizontal float animation only
645
+ CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
646
+ floatAnimationX.fromValue = @(-4.0);
647
+ floatAnimationX.toValue = @(4.0);
648
+ floatAnimationX.duration = 1.0; // Much faster animation
649
+ floatAnimationX.repeatCount = HUGE_VALF;
650
+ floatAnimationX.autoreverses = YES;
651
+ floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
652
+ [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
653
+
654
+ // Position info label at overlay center, above button
655
+ NSPoint labelCenter = NSMakePoint(
656
+ (width - [infoLabel frame].size.width) / 2, // Center horizontally
657
+ buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
658
+ );
659
+ [infoLabel setFrameOrigin:labelCenter];
660
+
661
+ // Position cancel button below the main button
662
+ NSButton *cancelButton = nil;
663
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
664
+ if ([subview isKindOfClass:[NSButton class]] &&
665
+ [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
666
+ cancelButton = (NSButton*)subview;
667
+ break;
668
+ }
669
+ }
670
+
671
+ if (cancelButton) {
672
+ NSSize cancelButtonSize = [cancelButton frame].size;
673
+ NSPoint cancelButtonCenter = NSMakePoint(
674
+ (width - cancelButtonSize.width) / 2,
675
+ buttonCenter.y - buttonSize.height - 20 // 20px below main button
676
+ );
677
+ [cancelButton setFrameOrigin:cancelButtonCenter];
799
678
  }
800
679
  }
801
- }
802
-
803
- NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
804
- [g_overlayWindow level], [g_overlayWindow alphaValue],
805
- [g_overlayWindow isVisible] ? "YES" : "NO");
806
-
807
- // Update overlay view states - highlight window under cursor and check selection
808
- if (g_overlayView && [g_overlayView isKindOfClass:[WindowSelectorOverlayView class]]) {
809
- WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
810
680
 
811
- // Check if this window is under cursor (highlight)
812
- BOOL isUnderCursor = (windowUnderCursor &&
813
- [g_currentWindowUnderCursor isEqualToDictionary:windowUnderCursor]);
681
+ [g_overlayWindow orderFront:nil];
682
+ [g_overlayWindow makeKeyAndOrderFront:nil];
683
+
684
+ // Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
685
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
686
+ if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
687
+ [subview setWantsLayer:YES];
688
+ if (subview.layer) {
689
+ subview.layer.borderWidth = 0.0;
690
+ subview.layer.borderColor = [[NSColor clearColor] CGColor];
691
+ subview.layer.masksToBounds = YES;
692
+ subview.layer.shadowOpacity = 0.0;
693
+ subview.layer.shadowRadius = 0.0;
694
+ subview.layer.shadowOffset = NSMakeSize(0, 0);
695
+ }
696
+ }
697
+ }
814
698
 
815
- // Check if this is the clicked window (visual selection)
816
- BOOL isSelected = (g_clickedWindowInfo &&
817
- [g_clickedWindowInfo isEqualToDictionary:windowUnderCursor]);
699
+ NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
700
+ [g_overlayWindow level], [g_overlayWindow alphaValue],
701
+ [g_overlayWindow isVisible] ? "YES" : "NO");
702
+ } else if (!windowUnderCursor && g_currentWindowUnderCursor) {
703
+ // No window under cursor, hide overlay
704
+ NSString *leftWindowTitle = [g_currentWindowUnderCursor objectForKey:@"title"] ?: @"Untitled";
705
+ NSString *leftAppName = [g_currentWindowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
818
706
 
819
- // Allow highlighting on all windows (including selected) but keep selection state
820
- overlayView.isActiveWindow = isUnderCursor; // Highlight if under cursor
821
- overlayView.isSelectedWindow = isSelected; // Keep selection state
707
+ NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
822
708
 
823
- NSLog(@"🎯 Overlay State Updated: Active=%s, Selected=%s",
824
- isUnderCursor ? "YES" : "NO", isSelected ? "YES" : "NO");
709
+ [g_overlayWindow orderOut:nil];
710
+ [g_currentWindowUnderCursor release];
711
+ g_currentWindowUnderCursor = nil;
825
712
  }
826
713
  }
827
714
  }
@@ -862,22 +749,9 @@ void cleanupWindowSelector() {
862
749
  g_allWindows = nil;
863
750
  }
864
751
 
865
- // Clean up selected overlay
866
- if (g_selectedOverlayView) {
867
- g_selectedOverlayView.isSelectedWindow = NO;
868
- g_selectedOverlayView = nil;
869
- }
870
-
871
- // Clear clicked window info (visual selection)
872
- if (g_clickedWindowInfo) {
873
- [g_clickedWindowInfo release];
874
- g_clickedWindowInfo = nil;
875
- }
876
-
877
- // Clear selected window info (recording selection)
878
- if (g_selectedWindowInfo) {
879
- [g_selectedWindowInfo release];
880
- g_selectedWindowInfo = nil;
752
+ if (g_currentWindowUnderCursor) {
753
+ [g_currentWindowUnderCursor release];
754
+ g_currentWindowUnderCursor = nil;
881
755
  }
882
756
  }
883
757
 
@@ -1549,31 +1423,13 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1549
1423
 
1550
1424
  @try {
1551
1425
  // Get all windows
1552
- g_allWindows = [[getAllSelectableWindows() mutableCopy] retain];
1426
+ g_allWindows = [getAllSelectableWindows() retain];
1553
1427
 
1554
1428
  if (!g_allWindows || [g_allWindows count] == 0) {
1555
1429
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
1556
1430
  return env.Null();
1557
1431
  }
1558
1432
 
1559
- // Clear any previous selection
1560
- if (g_selectedWindowInfo) {
1561
- [g_selectedWindowInfo release];
1562
- g_selectedWindowInfo = nil;
1563
- }
1564
- if (g_clickedWindowInfo) {
1565
- [g_clickedWindowInfo release];
1566
- g_clickedWindowInfo = nil;
1567
- }
1568
- if (g_selectedOverlayView) {
1569
- g_selectedOverlayView.isSelectedWindow = NO;
1570
- g_selectedOverlayView = nil;
1571
- }
1572
- if (g_currentWindowUnderCursor) {
1573
- [g_currentWindowUnderCursor release];
1574
- g_currentWindowUnderCursor = nil;
1575
- }
1576
-
1577
1433
  // Create overlay window (initially hidden)
1578
1434
  NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
1579
1435
  g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
@@ -1608,15 +1464,6 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1608
1464
  g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
1609
1465
  [g_overlayWindow setContentView:g_overlayView];
1610
1466
 
1611
- // Initialize overlay view properties
1612
- [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
1613
- [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
1614
-
1615
- // Set initial window under cursor for highlighting
1616
- if (g_allWindows && [g_allWindows count] > 0) {
1617
- g_currentWindowUnderCursor = [[g_allWindows objectAtIndex:0] retain];
1618
- }
1619
-
1620
1467
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
1621
1468
 
1622
1469
  // Force content view to have no borders
@@ -1839,8 +1686,9 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1839
1686
  result.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
1840
1687
  result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1841
1688
 
1842
- // Don't clear selected window info - just read it
1843
- // The info will be cleared when recording actually starts
1689
+ // Clear selected window info after reading
1690
+ [g_selectedWindowInfo release];
1691
+ g_selectedWindowInfo = nil;
1844
1692
 
1845
1693
  return result;
1846
1694