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