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.
- package/package.json +1 -1
- package/src/window_selector.mm +330 -254
- package/src/window_selector.mm.bak +2051 -0
package/src/window_selector.mm
CHANGED
|
@@ -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 =
|
|
71
|
+
self.isActiveWindow = NO; // Default to inactive (no mouse highlighting)
|
|
72
|
+
self.isSelectedWindow = NO; // Default to not selected
|
|
67
73
|
|
|
68
|
-
// Set
|
|
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.
|
|
78
|
-
//
|
|
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
|
-
|
|
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 (
|
|
223
|
-
|
|
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
|
|
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
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
//
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
infoLabel
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
700
|
-
|
|
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
|
-
|
|
764
|
+
// Update states
|
|
765
|
+
overlayView.isActiveWindow = NO; // No more mouse-based highlighting
|
|
766
|
+
overlayView.isSelectedWindow = isSelected;
|
|
708
767
|
|
|
709
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|