node-mac-recorder 2.7.1 → 2.7.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,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 (will be set by updateOverlay)
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 (highlighted): 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 highlighted 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,57 @@ 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 toggle window selection
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
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 VIA CLICK: %@ - \"%@\"",
144
+ [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
145
+ [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
146
+ } else {
147
+ // Select this window
148
+ self.isSelectedWindow = YES;
149
+
150
+ // Update global selected window info
151
+ if (g_selectedWindowInfo) {
152
+ [g_selectedWindowInfo release];
153
+ }
154
+ g_selectedWindowInfo = [self.windowInfo retain];
155
+
156
+ // Update global selected overlay reference
157
+ g_selectedOverlayView = self;
158
+
159
+ NSLog(@"🎯 WINDOW SELECTED VIA CLICK: %@ - \"%@\"",
160
+ [self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
161
+ [self.windowInfo objectForKey:@"title"] ?: @"Untitled");
162
+ }
163
+
164
+ // Update overlay appearance
165
+ [self updateAppearance];
166
+ }
167
+ }
168
+
104
169
  // Layer-based approach, no custom drawing needed
105
170
 
106
171
  @end
@@ -219,9 +284,14 @@ void updateScreenOverlays();
219
284
 
220
285
  @implementation WindowSelectorDelegate
221
286
  - (void)selectButtonClicked:(id)sender {
222
- if (g_currentWindowUnderCursor) {
223
- g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
287
+ if (g_selectedWindowInfo) {
288
+ // Use the selected window info (from click) instead of cursor position
289
+ NSLog(@"🎯 START RECORDING: Selected window confirmed - %@ - \"%@\"",
290
+ [g_selectedWindowInfo objectForKey:@"appName"] ?: @"Unknown",
291
+ [g_selectedWindowInfo objectForKey:@"title"] ?: @"Untitled");
224
292
  cleanupWindowSelector();
293
+ } else {
294
+ NSLog(@"⚠️ No window selected - cannot start recording");
225
295
  }
226
296
  }
227
297
 
@@ -444,271 +514,294 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
444
514
  }
445
515
  }
446
516
 
447
- // Update overlay to highlight window under cursor
517
+ // Update overlay to show window under cursor with highlighting (but no auto-bring-to-front)
448
518
  void updateOverlay() {
449
519
  @autoreleasepool {
450
520
  if (!g_isWindowSelecting || !g_overlayWindow) return;
451
521
 
452
- // Get current cursor position
522
+ // Get current cursor position for highlighting
453
523
  NSPoint mouseLocation = [NSEvent mouseLocation];
454
- // Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
455
524
  NSScreen *mainScreen = [NSScreen mainScreen];
456
525
  CGFloat screenHeight = [mainScreen frame].size.height;
457
526
  CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
458
527
 
459
- // Find window under cursor
528
+ // Find window under cursor for highlighting
460
529
  NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
461
530
 
531
+ // Update current window under cursor for highlighting
462
532
  if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
463
- // Update current window
464
533
  [g_currentWindowUnderCursor release];
465
534
  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
- }
535
+ }
536
+
537
+ // If no window under cursor, show first available window
538
+ if (!windowUnderCursor) {
539
+ if (g_allWindows && [g_allWindows count] > 0) {
540
+ windowUnderCursor = [g_allWindows objectAtIndex:0];
541
+ } else {
542
+ return; // No windows available
492
543
  }
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
- }
544
+ }
545
+
546
+ // Update overlay position and size
547
+ int x = [[windowUnderCursor objectForKey:@"x"] intValue];
548
+ int y = [[windowUnderCursor objectForKey:@"y"] intValue];
549
+ int width = [[windowUnderCursor objectForKey:@"width"] intValue];
550
+ int height = [[windowUnderCursor objectForKey:@"height"] intValue];
551
+
552
+ // Find which screen contains the window center
553
+ NSArray *screens = [NSScreen screens];
554
+ NSScreen *windowScreen = nil;
555
+ CGFloat windowCenterX = x + width / 2;
556
+ CGFloat windowCenterY = y + height / 2;
557
+
558
+ for (NSScreen *screen in screens) {
559
+ NSRect screenFrame = [screen frame];
560
+ // Convert screen frame to CGWindow coordinates
561
+ CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
562
+ CGFloat screenBottom = screenFrame.origin.y;
563
+ CGFloat screenLeft = screenFrame.origin.x;
564
+ CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
565
+
566
+ if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
567
+ windowCenterY >= screenBottom && windowCenterY <= screenTop) {
568
+ windowScreen = screen;
569
+ break;
526
570
  }
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;
571
+ }
572
+
573
+ // Use main screen if no specific screen found
574
+ if (!windowScreen) windowScreen = [NSScreen mainScreen];
575
+
576
+ // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
577
+ NSRect screenFrame = [windowScreen frame];
578
+ CGFloat windowScreenHeight = screenFrame.size.height;
579
+ CGFloat adjustedY = windowScreenHeight - y - height;
580
+
581
+ // Window coordinates are in global space, overlay frame should be screen-relative
582
+ // Keep X coordinate as-is (already in global space which is what we want)
583
+ // Only convert Y from top-left to bottom-left coordinate system
584
+ NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
585
+
586
+ NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
587
+ NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
588
+
589
+ NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
590
+ NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
591
+ NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
592
+ overlayFrame.origin.x, overlayFrame.origin.y,
593
+ overlayFrame.size.width, overlayFrame.size.height,
594
+ [g_overlayWindow level]);
595
+
596
+ // Bring window to front if enabled
597
+ if (g_bringToFrontEnabled) {
598
+ int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
599
+ if (windowId > 0) {
600
+ bool success = bringWindowToFront(windowId);
601
+ if (!success) {
602
+ NSLog(@" ⚠️ Failed to bring window to front");
540
603
  }
541
604
  }
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];
605
+ }
606
+
607
+ // Ensure overlay is on the correct screen
608
+ [g_overlayWindow setFrame:overlayFrame display:YES];
609
+
610
+ // Update overlay view window info
611
+ [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:windowUnderCursor];
612
+
613
+ // Don't automatically mark as selected - wait for user click
614
+ [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
615
+ [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
616
+
617
+ // Add/update info label above button
618
+ NSTextField *infoLabel = nil;
619
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
620
+ if ([subview isKindOfClass:[NSTextField class]]) {
621
+ infoLabel = (NSTextField*)subview;
622
+ break;
561
623
  }
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
- }
624
+ }
625
+
626
+ if (!infoLabel) {
627
+ infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
628
+ [infoLabel setEditable:NO];
629
+ [infoLabel setSelectable:NO];
630
+ [infoLabel setBezeled:NO];
631
+ [infoLabel setDrawsBackground:NO];
632
+ [infoLabel setAlignment:NSTextAlignmentCenter];
633
+ [infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
634
+ [infoLabel setTextColor:[NSColor whiteColor]];
635
+
636
+ // Force no borders on info label
637
+ [infoLabel setWantsLayer:YES];
638
+ infoLabel.layer.borderWidth = 0.0;
639
+ infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
640
+ infoLabel.layer.cornerRadius = 0.0;
641
+ infoLabel.layer.masksToBounds = YES;
642
+
643
+ [g_overlayWindow.contentView addSubview:infoLabel];
644
+ }
645
+
646
+ // Add/update app icon
647
+ NSImageView *appIconView = nil;
648
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
649
+ if ([subview isKindOfClass:[NSImageView class]]) {
650
+ appIconView = (NSImageView*)subview;
651
+ break;
570
652
  }
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);
653
+ }
654
+
655
+ if (!appIconView) {
656
+ appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
657
+ [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
658
+ [appIconView setWantsLayer:YES];
659
+ [appIconView.layer setCornerRadius:8.0];
660
+ [appIconView.layer setMasksToBounds:YES];
661
+ [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
662
+
663
+ // Force no borders on app icon view
664
+ appIconView.layer.borderWidth = 0.0;
665
+ appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
666
+ appIconView.layer.shadowOpacity = 0.0;
667
+ appIconView.layer.shadowRadius = 0.0;
668
+ appIconView.layer.shadowOffset = NSMakeSize(0, 0);
669
+
670
+ [g_overlayWindow.contentView addSubview:appIconView];
671
+ NSLog(@"🖼️ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
672
+ appIconView.frame.origin.x, appIconView.frame.origin.y,
673
+ appIconView.frame.size.width, appIconView.frame.size.height);
674
+ }
675
+
676
+ // Get app icon using NSWorkspace
677
+ NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
678
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
679
+ NSArray *runningApps = [workspace runningApplications];
680
+ NSImage *appIcon = nil;
681
+
682
+ for (NSRunningApplication *app in runningApps) {
683
+ if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
684
+ appIcon = [app icon];
685
+ break;
591
686
  }
687
+ }
688
+
689
+ // Fallback to generic app icon if not found
690
+ if (!appIcon) {
691
+ appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
692
+ NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
693
+ } else {
694
+ NSLog(@"✅ Found app icon for: %@", iconAppName);
695
+ }
696
+
697
+ [appIconView setImage:appIcon];
698
+ NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
699
+
700
+ // Update label text
701
+ NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
702
+ NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
703
+ [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
704
+
705
+ // Position buttons - Start Record in center, Cancel below it
706
+ if (g_selectButton) {
707
+ NSSize buttonSize = [g_selectButton frame].size;
708
+ NSPoint buttonCenter = NSMakePoint(
709
+ (width - buttonSize.width) / 2,
710
+ (height - buttonSize.height) / 2 + 15 // Slightly above center
711
+ );
712
+ [g_selectButton setFrameOrigin:buttonCenter];
592
713
 
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;
714
+ // Position app icon above text label
715
+ NSPoint iconCenter = NSMakePoint(
716
+ (width - 96) / 2, // Center horizontally (icon is 96px wide)
717
+ buttonCenter.y + buttonSize.height + 60 + 10 // Above label + text height + margin
718
+ );
719
+ [appIconView setFrameOrigin:iconCenter];
720
+ NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
721
+ iconCenter.x, iconCenter.y, (float)width, (float)height);
722
+
723
+ // Add fast horizontal floating animation after positioning
724
+ [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
725
+ [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
726
+
727
+ // Faster horizontal float animation only
728
+ CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
729
+ floatAnimationX.fromValue = @(-4.0);
730
+ floatAnimationX.toValue = @(4.0);
731
+ floatAnimationX.duration = 1.0; // Much faster animation
732
+ floatAnimationX.repeatCount = HUGE_VALF;
733
+ floatAnimationX.autoreverses = YES;
734
+ floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
735
+ [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
736
+
737
+ // Position info label at overlay center, above button
738
+ NSPoint labelCenter = NSMakePoint(
739
+ (width - [infoLabel frame].size.width) / 2, // Center horizontally
740
+ buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
741
+ );
742
+ [infoLabel setFrameOrigin:labelCenter];
598
743
 
599
- for (NSRunningApplication *app in runningApps) {
600
- if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
601
- appIcon = [app icon];
744
+ // Position cancel button below the main button
745
+ NSButton *cancelButton = nil;
746
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
747
+ if ([subview isKindOfClass:[NSButton class]] &&
748
+ [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
749
+ cancelButton = (NSButton*)subview;
602
750
  break;
603
751
  }
604
752
  }
605
753
 
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
754
+ if (cancelButton) {
755
+ NSSize cancelButtonSize = [cancelButton frame].size;
756
+ NSPoint cancelButtonCenter = NSMakePoint(
757
+ (width - cancelButtonSize.width) / 2,
758
+ buttonCenter.y - buttonSize.height - 20 // 20px below main button
628
759
  );
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
- }
760
+ [cancelButton setFrameOrigin:cancelButtonCenter];
679
761
  }
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
- }
762
+ }
763
+
764
+ // Show overlay for the current window (will be selected when clicked)
765
+ [g_overlayWindow orderFront:nil];
766
+ [g_overlayWindow makeKeyAndOrderFront:nil];
767
+
768
+ // Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
769
+ for (NSView *subview in [g_overlayWindow.contentView subviews]) {
770
+ if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
771
+ [subview setWantsLayer:YES];
772
+ if (subview.layer) {
773
+ subview.layer.borderWidth = 0.0;
774
+ subview.layer.borderColor = [[NSColor clearColor] CGColor];
775
+ subview.layer.masksToBounds = YES;
776
+ subview.layer.shadowOpacity = 0.0;
777
+ subview.layer.shadowRadius = 0.0;
778
+ subview.layer.shadowOffset = NSMakeSize(0, 0);
696
779
  }
697
780
  }
781
+ }
782
+
783
+ NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
784
+ [g_overlayWindow level], [g_overlayWindow alphaValue],
785
+ [g_overlayWindow isVisible] ? "YES" : "NO");
786
+
787
+ // Update overlay view states - highlight window under cursor and check selection
788
+ if (g_overlayView && [g_overlayView isKindOfClass:[WindowSelectorOverlayView class]]) {
789
+ WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
698
790
 
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";
791
+ // Check if this window is under cursor (highlight)
792
+ BOOL isUnderCursor = (windowUnderCursor &&
793
+ [g_currentWindowUnderCursor isEqualToDictionary:windowUnderCursor]);
706
794
 
707
- NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
795
+ // Check if this is the selected window
796
+ BOOL isSelected = (g_selectedWindowInfo &&
797
+ [g_selectedWindowInfo isEqualToDictionary:windowUnderCursor]);
708
798
 
709
- [g_overlayWindow orderOut:nil];
710
- [g_currentWindowUnderCursor release];
711
- g_currentWindowUnderCursor = nil;
799
+ // Update states
800
+ overlayView.isActiveWindow = isUnderCursor; // Highlight if under cursor
801
+ overlayView.isSelectedWindow = isSelected;
802
+
803
+ NSLog(@"🎯 Overlay State Updated: Active=%s, Selected=%s",
804
+ isUnderCursor ? "YES" : "NO", isSelected ? "YES" : "NO");
712
805
  }
713
806
  }
714
807
  }
@@ -749,9 +842,10 @@ void cleanupWindowSelector() {
749
842
  g_allWindows = nil;
750
843
  }
751
844
 
752
- if (g_currentWindowUnderCursor) {
753
- [g_currentWindowUnderCursor release];
754
- g_currentWindowUnderCursor = nil;
845
+ // Clean up selected overlay
846
+ if (g_selectedOverlayView) {
847
+ g_selectedOverlayView.isSelectedWindow = NO;
848
+ g_selectedOverlayView = nil;
755
849
  }
756
850
  }
757
851
 
@@ -1423,13 +1517,27 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1423
1517
 
1424
1518
  @try {
1425
1519
  // Get all windows
1426
- g_allWindows = [getAllSelectableWindows() retain];
1520
+ g_allWindows = [[getAllSelectableWindows() mutableCopy] retain];
1427
1521
 
1428
1522
  if (!g_allWindows || [g_allWindows count] == 0) {
1429
1523
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
1430
1524
  return env.Null();
1431
1525
  }
1432
1526
 
1527
+ // Clear any previous selection
1528
+ if (g_selectedWindowInfo) {
1529
+ [g_selectedWindowInfo release];
1530
+ g_selectedWindowInfo = nil;
1531
+ }
1532
+ if (g_selectedOverlayView) {
1533
+ g_selectedOverlayView.isSelectedWindow = NO;
1534
+ g_selectedOverlayView = nil;
1535
+ }
1536
+ if (g_currentWindowUnderCursor) {
1537
+ [g_currentWindowUnderCursor release];
1538
+ g_currentWindowUnderCursor = nil;
1539
+ }
1540
+
1433
1541
  // Create overlay window (initially hidden)
1434
1542
  NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
1435
1543
  g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
@@ -1464,6 +1572,15 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1464
1572
  g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
1465
1573
  [g_overlayWindow setContentView:g_overlayView];
1466
1574
 
1575
+ // Initialize overlay view properties
1576
+ [(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
1577
+ [(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
1578
+
1579
+ // Set initial window under cursor for highlighting
1580
+ if (g_allWindows && [g_allWindows count] > 0) {
1581
+ g_currentWindowUnderCursor = [[g_allWindows objectAtIndex:0] retain];
1582
+ }
1583
+
1467
1584
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
1468
1585
 
1469
1586
  // Force content view to have no borders