node-mac-recorder 2.10.0 → 2.10.3

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,14 +7,10 @@
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
16
  static NSDictionary *g_selectedWindowInfo = nil;
@@ -58,7 +54,6 @@ void updateScreenOverlays();
58
54
  @interface WindowSelectorOverlayView : NSView
59
55
  @property (nonatomic, strong) NSDictionary *windowInfo;
60
56
  @property (nonatomic) BOOL isActiveWindow;
61
- @property (nonatomic) BOOL isSelectedWindow;
62
57
  @end
63
58
 
64
59
  @implementation WindowSelectorOverlayView
@@ -68,10 +63,9 @@ void updateScreenOverlays();
68
63
  if (self) {
69
64
  // Use layer for background instead of custom drawing
70
65
  self.wantsLayer = YES;
71
- self.isActiveWindow = NO; // Default to inactive (will be set by updateOverlay)
72
- self.isSelectedWindow = NO; // Default to not selected
66
+ self.isActiveWindow = YES; // Default to active for current window under mouse
73
67
 
74
- // Set initial appearance
68
+ // Set purple background with border using layer
75
69
  [self updateAppearance];
76
70
 
77
71
  // Window selector overlay view created
@@ -80,27 +74,19 @@ void updateScreenOverlays();
80
74
  }
81
75
 
82
76
  - (void)updateAppearance {
83
- if (self.isSelectedWindow) {
84
- // Selected window: same background but thick border (3px)
85
- self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor]; // Same as highlight
86
- self.layer.borderColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:1.0] CGColor];
87
- self.layer.borderWidth = 3.0; // Thick border for selected window
88
- // Selected window appearance set
89
- } else if (self.isActiveWindow) {
90
- // Active window (highlighted): thin border (1px)
77
+ if (self.isActiveWindow) {
78
+ // Active window: brighter background
91
79
  self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor];
92
- self.layer.borderColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.8] CGColor];
93
- self.layer.borderWidth = 1.0; // Thin border for highlighted window
94
80
  // Active window appearance set
95
81
  } else {
96
- // Inactive window: no border
82
+ // Inactive window: dimmer background
97
83
  self.layer.backgroundColor = [[NSColor colorWithRed:0.4 green:0.2 blue:0.6 alpha:0.25] CGColor];
98
- self.layer.borderColor = [[NSColor clearColor] CGColor];
99
- self.layer.borderWidth = 0.0; // No border for inactive window
100
84
  // Inactive window appearance set
101
85
  }
102
86
 
103
- // Common styling
87
+ // No border to match screen selector
88
+ self.layer.borderColor = [[NSColor clearColor] CGColor];
89
+ self.layer.borderWidth = 0.0;
104
90
  self.layer.cornerRadius = 8.0;
105
91
  self.layer.masksToBounds = YES;
106
92
  self.layer.shadowOpacity = 0.0;
@@ -115,65 +101,6 @@ void updateScreenOverlays();
115
101
  }
116
102
  }
117
103
 
118
- - (void)setIsSelectedWindow:(BOOL)isSelectedWindow {
119
- if (_isSelectedWindow != isSelectedWindow) {
120
- _isSelectedWindow = isSelectedWindow;
121
- [self updateAppearance];
122
- }
123
- }
124
-
125
- - (void)mouseDown:(NSEvent *)event {
126
- // Handle mouse click to toggle window selection (ONLY selection, no recording start)
127
- if (self.windowInfo) {
128
- // Check if this window is already selected
129
- BOOL wasSelected = (g_selectedWindowInfo &&
130
- [g_selectedWindowInfo isEqualToDictionary:self.windowInfo]);
131
-
132
- if (wasSelected) {
133
- // Deselect this window - return to normal highlight behavior
134
- self.isSelectedWindow = NO;
135
-
136
- // Clear global selection
137
- if (g_selectedWindowInfo) {
138
- [g_selectedWindowInfo release];
139
- g_selectedWindowInfo = nil;
140
- }
141
- g_selectedOverlayView = nil;
142
-
143
- NSLog(@"🚫 WINDOW DESELECTED - Back to normal highlight mode: %@ - \"%@\"",
144
- [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
145
- [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
146
- } else {
147
- // Deselect previous window if any
148
- if (g_selectedOverlayView && g_selectedOverlayView != self) {
149
- g_selectedOverlayView.isSelectedWindow = NO;
150
- [g_selectedOverlayView updateAppearance];
151
- }
152
-
153
- // Select this window
154
- self.isSelectedWindow = YES;
155
-
156
- // Update global selected window info
157
- if (g_selectedWindowInfo) {
158
- [g_selectedWindowInfo release];
159
- }
160
- g_selectedWindowInfo = [self.windowInfo retain];
161
-
162
- // Update global selected overlay reference
163
- g_selectedOverlayView = self;
164
-
165
- NSLog(@"🎯 WINDOW SELECTED - Highlight blocked on this window: %@ - \"%@\"",
166
- [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
167
- [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
168
- }
169
-
170
- // Update appearance for this overlay
171
- [self updateAppearance];
172
-
173
- // DON'T start recording here - that's only for the "Start Record" button
174
- }
175
- }
176
-
177
104
  // Layer-based approach, no custom drawing needed
178
105
 
179
106
  @end
@@ -292,14 +219,9 @@ void updateScreenOverlays();
292
219
 
293
220
  @implementation WindowSelectorDelegate
294
221
  - (void)selectButtonClicked:(id)sender {
295
- if (g_selectedWindowInfo) {
296
- // Use the selected window info (from click) instead of cursor position
297
- NSLog(@"🎯 START RECORDING: Selected window confirmed - %@ - \"%@\"",
298
- [g_selectedWindowInfo objectForKey:@"appName"] ?: @"Unknown",
299
- [g_selectedWindowInfo objectForKey:@"title"] ?: @"Untitled");
222
+ if (g_currentWindowUnderCursor) {
223
+ g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
300
224
  cleanupWindowSelector();
301
- } else {
302
- NSLog(@"⚠️ No window selected - cannot start recording");
303
225
  }
304
226
  }
305
227
 
@@ -522,294 +444,271 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
522
444
  }
523
445
  }
524
446
 
525
- // Update overlay to show window under cursor with highlighting (but no auto-bring-to-front)
447
+ // Update overlay to highlight window under cursor
526
448
  void updateOverlay() {
527
449
  @autoreleasepool {
528
450
  if (!g_isWindowSelecting || !g_overlayWindow) return;
529
451
 
530
- // Get current cursor position for highlighting
452
+ // Get current cursor position
531
453
  NSPoint mouseLocation = [NSEvent mouseLocation];
454
+ // Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
532
455
  NSScreen *mainScreen = [NSScreen mainScreen];
533
456
  CGFloat screenHeight = [mainScreen frame].size.height;
534
457
  CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
535
458
 
536
- // Find window under cursor for highlighting
459
+ // Find window under cursor
537
460
  NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
538
461
 
539
- // Update current window under cursor for highlighting
540
462
  if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
463
+ // Update current window
541
464
  [g_currentWindowUnderCursor release];
542
465
  g_currentWindowUnderCursor = [windowUnderCursor retain];
543
- }
544
-
545
- // If no window under cursor, show first available window
546
- if (!windowUnderCursor) {
547
- if (g_allWindows && [g_allWindows count] > 0) {
548
- windowUnderCursor = [g_allWindows objectAtIndex:0];
549
- } else {
550
- 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
+ }
551
492
  }
552
- }
553
-
554
- // Update overlay position and size
555
- int x = [[windowUnderCursor objectForKey:@"x"] intValue];
556
- int y = [[windowUnderCursor objectForKey:@"y"] intValue];
557
- int width = [[windowUnderCursor objectForKey:@"width"] intValue];
558
- int height = [[windowUnderCursor objectForKey:@"height"] intValue];
559
-
560
- // Find which screen contains the window center
561
- NSArray *screens = [NSScreen screens];
562
- NSScreen *windowScreen = nil;
563
- CGFloat windowCenterX = x + width / 2;
564
- CGFloat windowCenterY = y + height / 2;
565
-
566
- for (NSScreen *screen in screens) {
567
- NSRect screenFrame = [screen frame];
568
- // Convert screen frame to CGWindow coordinates
569
- CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
570
- CGFloat screenBottom = screenFrame.origin.y;
571
- CGFloat screenLeft = screenFrame.origin.x;
572
- CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
573
-
574
- if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
575
- windowCenterY >= screenBottom && windowCenterY <= screenTop) {
576
- windowScreen = screen;
577
- 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
+ }
578
526
  }
579
- }
580
-
581
- // Use main screen if no specific screen found
582
- if (!windowScreen) windowScreen = [NSScreen mainScreen];
583
-
584
- // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
585
- NSRect screenFrame = [windowScreen frame];
586
- CGFloat windowScreenHeight = screenFrame.size.height;
587
- CGFloat adjustedY = windowScreenHeight - y - height;
588
-
589
- // Window coordinates are in global space, overlay frame should be screen-relative
590
- // Keep X coordinate as-is (already in global space which is what we want)
591
- // Only convert Y from top-left to bottom-left coordinate system
592
- NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
593
-
594
- NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
595
- NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
596
-
597
- NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
598
- NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
599
- NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
600
- overlayFrame.origin.x, overlayFrame.origin.y,
601
- overlayFrame.size.width, overlayFrame.size.height,
602
- [g_overlayWindow level]);
603
-
604
- // Bring window to front if enabled
605
- if (g_bringToFrontEnabled) {
606
- int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
607
- if (windowId > 0) {
608
- bool success = bringWindowToFront(windowId);
609
- if (!success) {
610
- 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;
611
540
  }
612
541
  }
613
- }
614
-
615
- // Ensure overlay is on the correct screen
616
- [g_overlayWindow setFrame:overlayFrame display:YES];
617
-
618
- // Update overlay view window info
619
- [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
620
-
621
- // Don't automatically mark as selected - wait for user click
622
- [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
623
- [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
624
-
625
- // Add/update info label above button
626
- NSTextField *infoLabel = nil;
627
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
628
- if ([subview isKindOfClass:[NSTextField class]]) {
629
- infoLabel = (NSTextField*)subview;
630
- 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];
631
561
  }
632
- }
633
-
634
- if (!infoLabel) {
635
- infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
636
- [infoLabel setEditable:NO];
637
- [infoLabel setSelectable:NO];
638
- [infoLabel setBezeled:NO];
639
- [infoLabel setDrawsBackground:NO];
640
- [infoLabel setAlignment:NSTextAlignmentCenter];
641
- [infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
642
- [infoLabel setTextColor:[NSColor whiteColor]];
643
-
644
- // Force no borders on info label
645
- [infoLabel setWantsLayer:YES];
646
- infoLabel.layer.borderWidth = 0.0;
647
- infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
648
- infoLabel.layer.cornerRadius = 0.0;
649
- infoLabel.layer.masksToBounds = YES;
650
-
651
- [g_overlayWindow.contentView addSubview:infoLabel];
652
- }
653
-
654
- // Add/update app icon
655
- NSImageView *appIconView = nil;
656
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
657
- if ([subview isKindOfClass:[NSImageView class]]) {
658
- appIconView = (NSImageView*)subview;
659
- 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
+ }
660
570
  }
661
- }
662
-
663
- if (!appIconView) {
664
- appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
665
- [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
666
- [appIconView setWantsLayer:YES];
667
- [appIconView.layer setCornerRadius:8.0];
668
- [appIconView.layer setMasksToBounds:YES];
669
- [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
670
-
671
- // Force no borders on app icon view
672
- appIconView.layer.borderWidth = 0.0;
673
- appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
674
- appIconView.layer.shadowOpacity = 0.0;
675
- appIconView.layer.shadowRadius = 0.0;
676
- appIconView.layer.shadowOffset = NSMakeSize(0, 0);
677
-
678
- [g_overlayWindow.contentView addSubview:appIconView];
679
- NSLog(@"🖼️ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
680
- appIconView.frame.origin.x, appIconView.frame.origin.y,
681
- appIconView.frame.size.width, appIconView.frame.size.height);
682
- }
683
-
684
- // Get app icon using NSWorkspace
685
- NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
686
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
687
- NSArray *runningApps = [workspace runningApplications];
688
- NSImage *appIcon = nil;
689
-
690
- for (NSRunningApplication *app in runningApps) {
691
- if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
692
- appIcon = [app icon];
693
- 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);
694
591
  }
695
- }
696
-
697
- // Fallback to generic app icon if not found
698
- if (!appIcon) {
699
- appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
700
- NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
701
- } else {
702
- NSLog(@"✅ Found app icon for: %@", iconAppName);
703
- }
704
-
705
- [appIconView setImage:appIcon];
706
- NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
707
-
708
- // Update label text
709
- NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
710
- NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
711
- [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
712
-
713
- // Position buttons - Start Record in center, Cancel below it
714
- if (g_selectButton) {
715
- NSSize buttonSize = [g_selectButton frame].size;
716
- NSPoint buttonCenter = NSMakePoint(
717
- (width - buttonSize.width) / 2,
718
- (height - buttonSize.height) / 2 + 15 // Slightly above center
719
- );
720
- [g_selectButton setFrameOrigin:buttonCenter];
721
592
 
722
- // Position app icon above text label
723
- NSPoint iconCenter = NSMakePoint(
724
- (width - 96) / 2, // Center horizontally (icon is 96px wide)
725
- buttonCenter.y + buttonSize.height + 60 + 10 // Above label + text height + margin
726
- );
727
- [appIconView setFrameOrigin:iconCenter];
728
- NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
729
- iconCenter.x, iconCenter.y, (float)width, (float)height);
730
-
731
- // Add fast horizontal floating animation after positioning
732
- [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
733
- [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
734
-
735
- // Faster horizontal float animation only
736
- CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
737
- floatAnimationX.fromValue = @(-4.0);
738
- floatAnimationX.toValue = @(4.0);
739
- floatAnimationX.duration = 1.0; // Much faster animation
740
- floatAnimationX.repeatCount = HUGE_VALF;
741
- floatAnimationX.autoreverses = YES;
742
- floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
743
- [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
744
-
745
- // Position info label at overlay center, above button
746
- NSPoint labelCenter = NSMakePoint(
747
- (width - [infoLabel frame].size.width) / 2, // Center horizontally
748
- buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
749
- );
750
- [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;
751
598
 
752
- // Position cancel button below the main button
753
- NSButton *cancelButton = nil;
754
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
755
- if ([subview isKindOfClass:[NSButton class]] &&
756
- [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
757
- cancelButton = (NSButton*)subview;
599
+ for (NSRunningApplication *app in runningApps) {
600
+ if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
601
+ appIcon = [app icon];
758
602
  break;
759
603
  }
760
604
  }
761
605
 
762
- if (cancelButton) {
763
- NSSize cancelButtonSize = [cancelButton frame].size;
764
- NSPoint cancelButtonCenter = NSMakePoint(
765
- (width - cancelButtonSize.width) / 2,
766
- buttonCenter.y - buttonSize.height - 20 // 20px below main button
767
- );
768
- [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);
769
612
  }
770
- }
771
-
772
- // Show overlay for the current window (will be selected when clicked)
773
- [g_overlayWindow orderFront:nil];
774
- [g_overlayWindow makeKeyAndOrderFront:nil];
775
-
776
- // Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
777
- for (NSView *subview in [g_overlayWindow.contentView subviews]) {
778
- if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
779
- [subview setWantsLayer:YES];
780
- if (subview.layer) {
781
- subview.layer.borderWidth = 0.0;
782
- subview.layer.borderColor = [[NSColor clearColor] CGColor];
783
- subview.layer.masksToBounds = YES;
784
- subview.layer.shadowOpacity = 0.0;
785
- subview.layer.shadowRadius = 0.0;
786
- 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];
787
678
  }
788
679
  }
789
- }
790
-
791
- NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
792
- [g_overlayWindow level], [g_overlayWindow alphaValue],
793
- [g_overlayWindow isVisible] ? "YES" : "NO");
794
-
795
- // Update overlay view states - highlight window under cursor and check selection
796
- if (g_overlayView && [g_overlayView isKindOfClass:[WindowSelectorOverlayView class]]) {
797
- WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
798
680
 
799
- // Check if this window is under cursor (highlight)
800
- BOOL isUnderCursor = (windowUnderCursor &&
801
- [g_currentWindowUnderCursor isEqualToDictionary:windowUnderCursor]);
681
+ [g_overlayWindow orderFront:nil];
682
+ [g_overlayWindow makeKeyAndOrderFront:nil];
802
683
 
803
- // Check if this is the selected window
804
- BOOL isSelected = (g_selectedWindowInfo &&
805
- [g_selectedWindowInfo isEqualToDictionary:windowUnderCursor]);
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
+ }
806
698
 
807
- // Allow highlighting on all windows (including selected) but keep selection state
808
- overlayView.isActiveWindow = isUnderCursor; // Highlight if under cursor
809
- overlayView.isSelectedWindow = isSelected; // Keep selection state
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";
810
706
 
811
- NSLog(@"🎯 Overlay State Updated: Active=%s, Selected=%s",
812
- isUnderCursor ? "YES" : "NO", isSelected ? "YES" : "NO");
707
+ NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
708
+
709
+ [g_overlayWindow orderOut:nil];
710
+ [g_currentWindowUnderCursor release];
711
+ g_currentWindowUnderCursor = nil;
813
712
  }
814
713
  }
815
714
  }
@@ -850,10 +749,9 @@ void cleanupWindowSelector() {
850
749
  g_allWindows = nil;
851
750
  }
852
751
 
853
- // Clean up selected overlay
854
- if (g_selectedOverlayView) {
855
- g_selectedOverlayView.isSelectedWindow = NO;
856
- g_selectedOverlayView = nil;
752
+ if (g_currentWindowUnderCursor) {
753
+ [g_currentWindowUnderCursor release];
754
+ g_currentWindowUnderCursor = nil;
857
755
  }
858
756
  }
859
757
 
@@ -1525,27 +1423,13 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1525
1423
 
1526
1424
  @try {
1527
1425
  // Get all windows
1528
- g_allWindows = [[getAllSelectableWindows() mutableCopy] retain];
1426
+ g_allWindows = [getAllSelectableWindows() retain];
1529
1427
 
1530
1428
  if (!g_allWindows || [g_allWindows count] == 0) {
1531
1429
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
1532
1430
  return env.Null();
1533
1431
  }
1534
1432
 
1535
- // Clear any previous selection
1536
- if (g_selectedWindowInfo) {
1537
- [g_selectedWindowInfo release];
1538
- g_selectedWindowInfo = nil;
1539
- }
1540
- if (g_selectedOverlayView) {
1541
- g_selectedOverlayView.isSelectedWindow = NO;
1542
- g_selectedOverlayView = nil;
1543
- }
1544
- if (g_currentWindowUnderCursor) {
1545
- [g_currentWindowUnderCursor release];
1546
- g_currentWindowUnderCursor = nil;
1547
- }
1548
-
1549
1433
  // Create overlay window (initially hidden)
1550
1434
  NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
1551
1435
  g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
@@ -1580,15 +1464,6 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1580
1464
  g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
1581
1465
  [g_overlayWindow setContentView:g_overlayView];
1582
1466
 
1583
- // Initialize overlay view properties
1584
- [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
1585
- [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
1586
-
1587
- // Set initial window under cursor for highlighting
1588
- if (g_allWindows && [g_allWindows count] > 0) {
1589
- g_currentWindowUnderCursor = [[g_allWindows objectAtIndex:0] retain];
1590
- }
1591
-
1592
1467
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
1593
1468
 
1594
1469
  // Force content view to have no borders