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.
- package/package.json +1 -1
- package/src/window_selector.mm +362 -245
- 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 (will be set by updateOverlay)
|
|
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 (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
|
-
|
|
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 (
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
infoLabel
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
[
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
|
|
795
|
+
// Check if this is the selected window
|
|
796
|
+
BOOL isSelected = (g_selectedWindowInfo &&
|
|
797
|
+
[g_selectedWindowInfo isEqualToDictionary:windowUnderCursor]);
|
|
708
798
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|