node-mac-recorder 2.7.1 → 2.7.2

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,10 +7,14 @@
7
7
  #import <Accessibility/Accessibility.h>
8
8
  #import <QuartzCore/QuartzCore.h>
9
9
 
10
+ // Forward declarations
11
+ @class WindowSelectorOverlayView;
12
+
10
13
  // Global state for window selection
11
14
  static bool g_isWindowSelecting = false;
12
15
  static NSWindow *g_overlayWindow = nil;
13
16
  static NSView *g_overlayView = nil;
17
+ static WindowSelectorOverlayView *g_selectedOverlayView = nil; // Track selected overlay
14
18
  static NSButton *g_selectButton = nil;
15
19
  static NSTimer *g_trackingTimer = nil;
16
20
  static NSDictionary *g_selectedWindowInfo = nil;
@@ -54,6 +58,7 @@ void updateScreenOverlays();
54
58
  @interface WindowSelectorOverlayView : NSView
55
59
  @property (nonatomic, strong) NSDictionary *windowInfo;
56
60
  @property (nonatomic) BOOL isActiveWindow;
61
+ @property (nonatomic) BOOL isSelectedWindow;
57
62
  @end
58
63
 
59
64
  @implementation WindowSelectorOverlayView
@@ -63,9 +68,10 @@ void updateScreenOverlays();
63
68
  if (self) {
64
69
  // Use layer for background instead of custom drawing
65
70
  self.wantsLayer = YES;
66
- self.isActiveWindow = YES; // Default to active for current window under mouse
71
+ self.isActiveWindow = NO; // Default to inactive (no mouse highlighting)
72
+ self.isSelectedWindow = NO; // Default to not selected
67
73
 
68
- // Set purple background with border using layer
74
+ // Set initial appearance
69
75
  [self updateAppearance];
70
76
 
71
77
  // Window selector overlay view created
@@ -74,19 +80,27 @@ void updateScreenOverlays();
74
80
  }
75
81
 
76
82
  - (void)updateAppearance {
77
- if (self.isActiveWindow) {
78
- // Active window: brighter background
83
+ if (self.isSelectedWindow) {
84
+ // Selected window: bright background with thick border
85
+ self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.6] CGColor];
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: brighter background with thin border
79
91
  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 active window
80
94
  // Active window appearance set
81
95
  } else {
82
- // Inactive window: dimmer background
96
+ // Inactive window: dimmer background with no border
83
97
  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
84
100
  // Inactive window appearance set
85
101
  }
86
102
 
87
- // No border to match screen selector
88
- self.layer.borderColor = [[NSColor clearColor] CGColor];
89
- self.layer.borderWidth = 0.0;
103
+ // Common styling
90
104
  self.layer.cornerRadius = 8.0;
91
105
  self.layer.masksToBounds = YES;
92
106
  self.layer.shadowOpacity = 0.0;
@@ -101,6 +115,37 @@ void updateScreenOverlays();
101
115
  }
102
116
  }
103
117
 
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 select this window
127
+ if (self.windowInfo) {
128
+ // Set this overlay as selected
129
+ self.isSelectedWindow = YES;
130
+
131
+ // Update global selected window info
132
+ if (g_selectedWindowInfo) {
133
+ [g_selectedWindowInfo release];
134
+ }
135
+ g_selectedWindowInfo = [self.windowInfo retain];
136
+
137
+ // Update global selected overlay reference
138
+ g_selectedOverlayView = self;
139
+
140
+ // Update overlay appearance
141
+ [self updateAppearance];
142
+
143
+ NSLog(@"🎯 WINDOW SELECTED VIA CLICK: %@ - \"%@\"",
144
+ [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
145
+ [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
146
+ }
147
+ }
148
+
104
149
  // Layer-based approach, no custom drawing needed
105
150
 
106
151
  @end
@@ -219,9 +264,14 @@ void updateScreenOverlays();
219
264
 
220
265
  @implementation WindowSelectorDelegate
221
266
  - (void)selectButtonClicked:(id)sender {
222
- if (g_currentWindowUnderCursor) {
223
- g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
267
+ if (g_selectedWindowInfo) {
268
+ // Use the selected window info (from click) instead of cursor position
269
+ NSLog(@"🎯 START RECORDING: Selected window confirmed - %@ - \"%@\"",
270
+ [g_selectedWindowInfo objectForKey:@"appName"] ?: @"Unknown",
271
+ [g_selectedWindowInfo objectForKey:@"title"] ?: @"Untitled");
224
272
  cleanupWindowSelector();
273
+ } else {
274
+ NSLog(@"⚠️ No window selected - cannot start recording");
225
275
  }
226
276
  }
227
277
 
@@ -444,271 +494,278 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
444
494
  }
445
495
  }
446
496
 
447
- // Update overlay to highlight window under cursor
497
+ // Update overlay to show selected window (no more mouse-based highlighting)
448
498
  void updateOverlay() {
449
499
  @autoreleasepool {
450
500
  if (!g_isWindowSelecting || !g_overlayWindow) return;
451
501
 
452
- // Get current cursor position
453
- NSPoint mouseLocation = [NSEvent mouseLocation];
454
- // Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
455
- NSScreen *mainScreen = [NSScreen mainScreen];
456
- CGFloat screenHeight = [mainScreen frame].size.height;
457
- CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
502
+ // Check if we have a selected window or if we should show overlay for selection
503
+ NSDictionary *windowUnderCursor = nil;
504
+ if (!g_selectedWindowInfo) {
505
+ // No window selected yet - show overlay for the first available window
506
+ if (g_allWindows && [g_allWindows count] > 0) {
507
+ windowUnderCursor = [g_allWindows objectAtIndex:0];
508
+ } else {
509
+ return; // No windows available
510
+ }
511
+ } else {
512
+ // Use selected window info
513
+ windowUnderCursor = g_selectedWindowInfo;
514
+ }
458
515
 
459
- // Find window under cursor
460
- NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
516
+ // Update overlay position and size
517
+ int x = [[windowUnderCursor objectForKey:@"x"] intValue];
518
+ int y = [[windowUnderCursor objectForKey:@"y"] intValue];
519
+ int width = [[windowUnderCursor objectForKey:@"width"] intValue];
520
+ int height = [[windowUnderCursor objectForKey:@"height"] intValue];
461
521
 
462
- if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
463
- // Update current window
464
- [g_currentWindowUnderCursor release];
465
- g_currentWindowUnderCursor = [windowUnderCursor retain];
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
- }
492
- }
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
- }
522
+ // Find which screen contains the window center
523
+ NSArray *screens = [NSScreen screens];
524
+ NSScreen *windowScreen = nil;
525
+ CGFloat windowCenterX = x + width / 2;
526
+ CGFloat windowCenterY = y + height / 2;
527
+
528
+ for (NSScreen *screen in screens) {
529
+ NSRect screenFrame = [screen frame];
530
+ // Convert screen frame to CGWindow coordinates
531
+ CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
532
+ CGFloat screenBottom = screenFrame.origin.y;
533
+ CGFloat screenLeft = screenFrame.origin.x;
534
+ CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
535
+
536
+ if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
537
+ windowCenterY >= screenBottom && windowCenterY <= screenTop) {
538
+ windowScreen = screen;
539
+ break;
526
540
  }
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;
541
+ }
542
+
543
+ // Use main screen if no specific screen found
544
+ if (!windowScreen) windowScreen = [NSScreen mainScreen];
545
+
546
+ // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
547
+ NSRect screenFrame = [windowScreen frame];
548
+ CGFloat screenHeight = screenFrame.size.height;
549
+ CGFloat adjustedY = screenHeight - y - height;
550
+
551
+ // Window coordinates are in global space, overlay frame should be screen-relative
552
+ // Keep X coordinate as-is (already in global space which is what we want)
553
+ // Only convert Y from top-left to bottom-left coordinate system
554
+ NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
555
+
556
+ NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
557
+ NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
558
+
559
+ NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
560
+ NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
561
+ NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
562
+ overlayFrame.origin.x, overlayFrame.origin.y,
563
+ overlayFrame.size.width, overlayFrame.size.height,
564
+ [g_overlayWindow level]);
565
+
566
+ // Bring window to front if enabled
567
+ if (g_bringToFrontEnabled) {
568
+ int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
569
+ if (windowId > 0) {
570
+ bool success = bringWindowToFront(windowId);
571
+ if (!success) {
572
+ NSLog(@" ⚠️ Failed to bring window to front");
540
573
  }
541
574
  }
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];
575
+ }
576
+
577
+ // Ensure overlay is on the correct screen
578
+ [g_overlayWindow setFrame:overlayFrame display:YES];
579
+
580
+ // Update overlay view window info
581
+ [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
582
+
583
+ // Don't automatically mark as selected - wait for user click
584
+ [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
585
+ [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
586
+
587
+ // Add/update info label above button
588
+ NSTextField *infoLabel = nil;
589
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
590
+ if ([subview isKindOfClass:[NSTextField class]]) {
591
+ infoLabel = (NSTextField*)subview;
592
+ break;
561
593
  }
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
- }
594
+ }
595
+
596
+ if (!infoLabel) {
597
+ infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
598
+ [infoLabel setEditable:NO];
599
+ [infoLabel setSelectable:NO];
600
+ [infoLabel setBezeled:NO];
601
+ [infoLabel setDrawsBackground:NO];
602
+ [infoLabel setAlignment:NSTextAlignmentCenter];
603
+ [infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
604
+ [infoLabel setTextColor:[NSColor whiteColor]];
605
+
606
+ // Force no borders on info label
607
+ [infoLabel setWantsLayer:YES];
608
+ infoLabel.layer.borderWidth = 0.0;
609
+ infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
610
+ infoLabel.layer.cornerRadius = 0.0;
611
+ infoLabel.layer.masksToBounds = YES;
612
+
613
+ [g_overlayWindow.contentView addSubview:infoLabel];
614
+ }
615
+
616
+ // Add/update app icon
617
+ NSImageView *appIconView = nil;
618
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
619
+ if ([subview isKindOfClass:[NSImageView class]]) {
620
+ appIconView = (NSImageView*)subview;
621
+ break;
570
622
  }
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);
623
+ }
624
+
625
+ if (!appIconView) {
626
+ appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
627
+ [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
628
+ [appIconView setWantsLayer:YES];
629
+ [appIconView.layer setCornerRadius:8.0];
630
+ [appIconView.layer setMasksToBounds:YES];
631
+ [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
632
+
633
+ // Force no borders on app icon view
634
+ appIconView.layer.borderWidth = 0.0;
635
+ appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
636
+ appIconView.layer.shadowOpacity = 0.0;
637
+ appIconView.layer.shadowRadius = 0.0;
638
+ appIconView.layer.shadowOffset = NSMakeSize(0, 0);
639
+
640
+ [g_overlayWindow.contentView addSubview:appIconView];
641
+ NSLog(@"🖼️ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
642
+ appIconView.frame.origin.x, appIconView.frame.origin.y,
643
+ appIconView.frame.size.width, appIconView.frame.size.height);
644
+ }
645
+
646
+ // Get app icon using NSWorkspace
647
+ NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
648
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
649
+ NSArray *runningApps = [workspace runningApplications];
650
+ NSImage *appIcon = nil;
651
+
652
+ for (NSRunningApplication *app in runningApps) {
653
+ if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
654
+ appIcon = [app icon];
655
+ break;
591
656
  }
657
+ }
658
+
659
+ // Fallback to generic app icon if not found
660
+ if (!appIcon) {
661
+ appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
662
+ NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
663
+ } else {
664
+ NSLog(@"✅ Found app icon for: %@", iconAppName);
665
+ }
666
+
667
+ [appIconView setImage:appIcon];
668
+ NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
669
+
670
+ // Update label text
671
+ NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
672
+ NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
673
+ [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
674
+
675
+ // Position buttons - Start Record in center, Cancel below it
676
+ if (g_selectButton) {
677
+ NSSize buttonSize = [g_selectButton frame].size;
678
+ NSPoint buttonCenter = NSMakePoint(
679
+ (width - buttonSize.width) / 2,
680
+ (height - buttonSize.height) / 2 + 15 // Slightly above center
681
+ );
682
+ [g_selectButton setFrameOrigin:buttonCenter];
592
683
 
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;
684
+ // Position app icon above text label
685
+ NSPoint iconCenter = NSMakePoint(
686
+ (width - 96) / 2, // Center horizontally (icon is 96px wide)
687
+ buttonCenter.y + buttonSize.height + 60 + 10 // Above label + text height + margin
688
+ );
689
+ [appIconView setFrameOrigin:iconCenter];
690
+ NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
691
+ iconCenter.x, iconCenter.y, (float)width, (float)height);
692
+
693
+ // Add fast horizontal floating animation after positioning
694
+ [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
695
+ [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
696
+
697
+ // Faster horizontal float animation only
698
+ CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
699
+ floatAnimationX.fromValue = @(-4.0);
700
+ floatAnimationX.toValue = @(4.0);
701
+ floatAnimationX.duration = 1.0; // Much faster animation
702
+ floatAnimationX.repeatCount = HUGE_VALF;
703
+ floatAnimationX.autoreverses = YES;
704
+ floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
705
+ [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
706
+
707
+ // Position info label at overlay center, above button
708
+ NSPoint labelCenter = NSMakePoint(
709
+ (width - [infoLabel frame].size.width) / 2, // Center horizontally
710
+ buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
711
+ );
712
+ [infoLabel setFrameOrigin:labelCenter];
598
713
 
599
- for (NSRunningApplication *app in runningApps) {
600
- if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
601
- appIcon = [app icon];
714
+ // Position cancel button below the main button
715
+ NSButton *cancelButton = nil;
716
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
717
+ if ([subview isKindOfClass:[NSButton class]] &&
718
+ [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
719
+ cancelButton = (NSButton*)subview;
602
720
  break;
603
721
  }
604
722
  }
605
723
 
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);
612
- }
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
724
+ if (cancelButton) {
725
+ NSSize cancelButtonSize = [cancelButton frame].size;
726
+ NSPoint cancelButtonCenter = NSMakePoint(
727
+ (width - cancelButtonSize.width) / 2,
728
+ buttonCenter.y - buttonSize.height - 20 // 20px below main button
628
729
  );
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];
678
- }
730
+ [cancelButton setFrameOrigin:cancelButtonCenter];
679
731
  }
680
-
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
- }
732
+ }
733
+
734
+ // Show overlay for the current window (will be selected when clicked)
735
+ [g_overlayWindow orderFront:nil];
736
+ [g_overlayWindow makeKeyAndOrderFront:nil];
737
+
738
+ // Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
739
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
740
+ if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
741
+ [subview setWantsLayer:YES];
742
+ if (subview.layer) {
743
+ subview.layer.borderWidth = 0.0;
744
+ subview.layer.borderColor = [[NSColor clearColor] CGColor];
745
+ subview.layer.masksToBounds = YES;
746
+ subview.layer.shadowOpacity = 0.0;
747
+ subview.layer.shadowRadius = 0.0;
748
+ subview.layer.shadowOffset = NSMakeSize(0, 0);
696
749
  }
697
750
  }
751
+ }
752
+
753
+ NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
754
+ [g_overlayWindow level], [g_overlayWindow alphaValue],
755
+ [g_overlayWindow isVisible] ? "YES" : "NO");
756
+
757
+ // Update overlay view states - check if this window is selected
758
+ if (g_overlayView && [g_overlayView isKindOfClass:[WindowSelectorOverlayView class]]) {
759
+ WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
698
760
 
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";
761
+ // Check if this is the selected window (true if we have selected window info)
762
+ BOOL isSelected = (g_selectedWindowInfo != nil);
706
763
 
707
- NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
764
+ // Update states
765
+ overlayView.isActiveWindow = NO; // No more mouse-based highlighting
766
+ overlayView.isSelectedWindow = isSelected;
708
767
 
709
- [g_overlayWindow orderOut:nil];
710
- [g_currentWindowUnderCursor release];
711
- g_currentWindowUnderCursor = nil;
768
+ NSLog(@"🎯 Overlay State Updated: Active=NO, Selected=%s", isSelected ? "YES" : "NO");
712
769
  }
713
770
  }
714
771
  }
@@ -749,9 +806,10 @@ void cleanupWindowSelector() {
749
806
  g_allWindows = nil;
750
807
  }
751
808
 
752
- if (g_currentWindowUnderCursor) {
753
- [g_currentWindowUnderCursor release];
754
- g_currentWindowUnderCursor = nil;
809
+ // Clean up selected overlay
810
+ if (g_selectedOverlayView) {
811
+ g_selectedOverlayView.isSelectedWindow = NO;
812
+ g_selectedOverlayView = nil;
755
813
  }
756
814
  }
757
815
 
@@ -1423,13 +1481,27 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1423
1481
 
1424
1482
  @try {
1425
1483
  // Get all windows
1426
- g_allWindows = [getAllSelectableWindows() retain];
1484
+ g_allWindows = [[getAllSelectableWindows() mutableCopy] retain];
1427
1485
 
1428
1486
  if (!g_allWindows || [g_allWindows count] == 0) {
1429
1487
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
1430
1488
  return env.Null();
1431
1489
  }
1432
1490
 
1491
+ // Clear any previous selection
1492
+ if (g_selectedWindowInfo) {
1493
+ [g_selectedWindowInfo release];
1494
+ g_selectedWindowInfo = nil;
1495
+ }
1496
+ if (g_selectedOverlayView) {
1497
+ g_selectedOverlayView.isSelectedWindow = NO;
1498
+ g_selectedOverlayView = nil;
1499
+ }
1500
+ if (g_currentWindowUnderCursor) {
1501
+ [g_currentWindowUnderCursor release];
1502
+ g_currentWindowUnderCursor = nil;
1503
+ }
1504
+
1433
1505
  // Create overlay window (initially hidden)
1434
1506
  NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
1435
1507
  g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
@@ -1464,6 +1536,10 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1464
1536
  g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
1465
1537
  [g_overlayWindow setContentView:g_overlayView];
1466
1538
 
1539
+ // Initialize overlay view properties
1540
+ [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
1541
+ [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
1542
+
1467
1543
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
1468
1544
 
1469
1545
  // Force content view to have no borders