node-mac-recorder 2.9.2 → 2.10.1
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 +255 -407
- package/src/window_selector.mm.bak +0 -2051
package/src/window_selector.mm
CHANGED
|
@@ -7,18 +7,13 @@
|
|
|
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
|
-
static NSDictionary *g_selectedWindowInfo = nil;
|
|
21
|
-
static NSDictionary *g_clickedWindowInfo = nil; // Separate state for clicked selection (NOT for recording)
|
|
16
|
+
static NSDictionary *g_selectedWindowInfo = nil;
|
|
22
17
|
static NSMutableArray *g_allWindows = nil;
|
|
23
18
|
static NSDictionary *g_currentWindowUnderCursor = nil;
|
|
24
19
|
static bool g_bringToFrontEnabled = true; // Default enabled
|
|
@@ -59,7 +54,6 @@ void updateScreenOverlays();
|
|
|
59
54
|
@interface WindowSelectorOverlayView : NSView
|
|
60
55
|
@property (nonatomic, strong) NSDictionary *windowInfo;
|
|
61
56
|
@property (nonatomic) BOOL isActiveWindow;
|
|
62
|
-
@property (nonatomic) BOOL isSelectedWindow;
|
|
63
57
|
@end
|
|
64
58
|
|
|
65
59
|
@implementation WindowSelectorOverlayView
|
|
@@ -69,10 +63,9 @@ void updateScreenOverlays();
|
|
|
69
63
|
if (self) {
|
|
70
64
|
// Use layer for background instead of custom drawing
|
|
71
65
|
self.wantsLayer = YES;
|
|
72
|
-
self.isActiveWindow =
|
|
73
|
-
self.isSelectedWindow = NO; // Default to not selected
|
|
66
|
+
self.isActiveWindow = YES; // Default to active for current window under mouse
|
|
74
67
|
|
|
75
|
-
// Set
|
|
68
|
+
// Set purple background with border using layer
|
|
76
69
|
[self updateAppearance];
|
|
77
70
|
|
|
78
71
|
// Window selector overlay view created
|
|
@@ -81,27 +74,19 @@ void updateScreenOverlays();
|
|
|
81
74
|
}
|
|
82
75
|
|
|
83
76
|
- (void)updateAppearance {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.layer.borderColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.8] CGColor];
|
|
94
|
-
self.layer.borderWidth = 1.0; // Thin border for highlighted window
|
|
95
|
-
// Active window appearance set
|
|
96
|
-
} else {
|
|
97
|
-
// Inactive window: no border
|
|
98
|
-
self.layer.backgroundColor = [[NSColor colorWithRed:0.4 green:0.2 blue:0.6 alpha:0.25] CGColor];
|
|
99
|
-
self.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
100
|
-
self.layer.borderWidth = 0.0; // No border for inactive window
|
|
101
|
-
// Inactive window appearance set
|
|
102
|
-
}
|
|
77
|
+
if (self.isActiveWindow) {
|
|
78
|
+
// Active window: brighter background
|
|
79
|
+
self.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.4 blue:0.9 alpha:0.4] CGColor];
|
|
80
|
+
// Active window appearance set
|
|
81
|
+
} else {
|
|
82
|
+
// Inactive window: dimmer background
|
|
83
|
+
self.layer.backgroundColor = [[NSColor colorWithRed:0.4 green:0.2 blue:0.6 alpha:0.25] CGColor];
|
|
84
|
+
// Inactive window appearance set
|
|
85
|
+
}
|
|
103
86
|
|
|
104
|
-
|
|
87
|
+
// No border to match screen selector
|
|
88
|
+
self.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
89
|
+
self.layer.borderWidth = 0.0;
|
|
105
90
|
self.layer.cornerRadius = 8.0;
|
|
106
91
|
self.layer.masksToBounds = YES;
|
|
107
92
|
self.layer.shadowOpacity = 0.0;
|
|
@@ -116,71 +101,6 @@ void updateScreenOverlays();
|
|
|
116
101
|
}
|
|
117
102
|
}
|
|
118
103
|
|
|
119
|
-
- (void)setIsSelectedWindow:(BOOL)isSelectedWindow {
|
|
120
|
-
if (_isSelectedWindow != isSelectedWindow) {
|
|
121
|
-
_isSelectedWindow = isSelectedWindow;
|
|
122
|
-
[self updateAppearance];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
- (void)mouseDown:(NSEvent *)event {
|
|
127
|
-
// Handle mouse click to toggle window selection (ONLY selection, no recording start)
|
|
128
|
-
if (self.windowInfo) {
|
|
129
|
-
// Check if this window is already selected
|
|
130
|
-
BOOL wasSelected = (g_clickedWindowInfo &&
|
|
131
|
-
[g_clickedWindowInfo isEqualToDictionary:self.windowInfo]);
|
|
132
|
-
|
|
133
|
-
if (wasSelected) {
|
|
134
|
-
// Deselect this window - return to normal highlight behavior
|
|
135
|
-
self.isSelectedWindow = NO;
|
|
136
|
-
|
|
137
|
-
// Clear global clicked selection (NOT recording selection)
|
|
138
|
-
if (g_clickedWindowInfo) {
|
|
139
|
-
[g_clickedWindowInfo release];
|
|
140
|
-
g_clickedWindowInfo = nil;
|
|
141
|
-
}
|
|
142
|
-
g_selectedOverlayView = nil;
|
|
143
|
-
|
|
144
|
-
NSLog(@"🚫 WINDOW DESELECTED - Back to normal highlight mode: %@ - \"%@\"",
|
|
145
|
-
[self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
|
|
146
|
-
[self.windowInfo objectForKey:@"title"] ?: @"Untitled");
|
|
147
|
-
} else {
|
|
148
|
-
// Deselect previous window if any
|
|
149
|
-
if (g_selectedOverlayView && g_selectedOverlayView != self) {
|
|
150
|
-
g_selectedOverlayView.isSelectedWindow = NO;
|
|
151
|
-
[g_selectedOverlayView updateAppearance];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Select this window (for visual selection only, NOT for recording)
|
|
155
|
-
self.isSelectedWindow = YES;
|
|
156
|
-
|
|
157
|
-
// Update global clicked window info (separate from recording selection)
|
|
158
|
-
if (g_clickedWindowInfo) {
|
|
159
|
-
[g_clickedWindowInfo release];
|
|
160
|
-
}
|
|
161
|
-
g_clickedWindowInfo = [self.windowInfo retain];
|
|
162
|
-
|
|
163
|
-
// Update global selected overlay reference
|
|
164
|
-
g_selectedOverlayView = self;
|
|
165
|
-
|
|
166
|
-
NSLog(@"🎯 WINDOW CLICKED - Visual selection only (NOT recording): %@ - \"%@\"",
|
|
167
|
-
[self.windowInfo objectForKey:@"appName"] ?: @"Unknown",
|
|
168
|
-
[self.windowInfo objectForKey:@"title"] ?: @"Untitled");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Update appearance for this overlay
|
|
172
|
-
[self updateAppearance];
|
|
173
|
-
|
|
174
|
-
// DON'T start recording here - that's only for the "Start Record" button
|
|
175
|
-
// Consume the event completely to prevent any propagation
|
|
176
|
-
NSLog(@"🛑 MOUSE EVENT CONSUMED - No recording started");
|
|
177
|
-
|
|
178
|
-
// IMPORTANT: Return YES to indicate we handled the event
|
|
179
|
-
// This prevents the event from propagating to other handlers
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
104
|
// Layer-based approach, no custom drawing needed
|
|
185
105
|
|
|
186
106
|
@end
|
|
@@ -299,19 +219,9 @@ void updateScreenOverlays();
|
|
|
299
219
|
|
|
300
220
|
@implementation WindowSelectorDelegate
|
|
301
221
|
- (void)selectButtonClicked:(id)sender {
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
if (g_selectedWindowInfo) {
|
|
305
|
-
[g_selectedWindowInfo release];
|
|
306
|
-
}
|
|
307
|
-
g_selectedWindowInfo = [g_clickedWindowInfo retain];
|
|
308
|
-
|
|
309
|
-
NSLog(@"🎯 START RECORDING: Clicked window confirmed for recording - %@ - \"%@\"",
|
|
310
|
-
[g_selectedWindowInfo objectForKey:@"appName"] ?: @"Unknown",
|
|
311
|
-
[g_selectedWindowInfo objectForKey:@"title"] ?: @"Untitled");
|
|
222
|
+
if (g_currentWindowUnderCursor) {
|
|
223
|
+
g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
|
|
312
224
|
cleanupWindowSelector();
|
|
313
|
-
} else {
|
|
314
|
-
NSLog(@"⚠️ No window clicked - cannot start recording");
|
|
315
225
|
}
|
|
316
226
|
}
|
|
317
227
|
|
|
@@ -534,294 +444,271 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
|
|
|
534
444
|
}
|
|
535
445
|
}
|
|
536
446
|
|
|
537
|
-
// Update overlay to
|
|
447
|
+
// Update overlay to highlight window under cursor
|
|
538
448
|
void updateOverlay() {
|
|
539
449
|
@autoreleasepool {
|
|
540
450
|
if (!g_isWindowSelecting || !g_overlayWindow) return;
|
|
541
451
|
|
|
542
|
-
// Get current cursor position
|
|
452
|
+
// Get current cursor position
|
|
543
453
|
NSPoint mouseLocation = [NSEvent mouseLocation];
|
|
454
|
+
// Convert from NSEvent coordinates (bottom-left) to CGWindow coordinates (top-left)
|
|
544
455
|
NSScreen *mainScreen = [NSScreen mainScreen];
|
|
545
456
|
CGFloat screenHeight = [mainScreen frame].size.height;
|
|
546
457
|
CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
|
|
547
458
|
|
|
548
|
-
// Find window under cursor
|
|
459
|
+
// Find window under cursor
|
|
549
460
|
NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
|
|
550
461
|
|
|
551
|
-
// Update current window under cursor for highlighting
|
|
552
462
|
if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
|
|
463
|
+
// Update current window
|
|
553
464
|
[g_currentWindowUnderCursor release];
|
|
554
465
|
g_currentWindowUnderCursor = [windowUnderCursor retain];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
+
}
|
|
563
492
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
}
|
|
590
526
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
|
|
605
|
-
|
|
606
|
-
NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
|
|
607
|
-
NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
|
|
608
|
-
|
|
609
|
-
NSLog(@"🎯 WINDOW DETECTED: %@ - \"%@\"", appName, windowTitle);
|
|
610
|
-
NSLog(@" 📍 Position: (%d, %d) 📏 Size: %d × %d", x, y, width, height);
|
|
611
|
-
NSLog(@" 🖥️ NSRect: (%.0f, %.0f, %.0f, %.0f) 🔝 Level: %ld",
|
|
612
|
-
overlayFrame.origin.x, overlayFrame.origin.y,
|
|
613
|
-
overlayFrame.size.width, overlayFrame.size.height,
|
|
614
|
-
[g_overlayWindow level]);
|
|
615
|
-
|
|
616
|
-
// Bring window to front if enabled
|
|
617
|
-
if (g_bringToFrontEnabled) {
|
|
618
|
-
int windowId = [[windowUnderCursor objectForKey:@"id"] intValue];
|
|
619
|
-
if (windowId > 0) {
|
|
620
|
-
bool success = bringWindowToFront(windowId);
|
|
621
|
-
if (!success) {
|
|
622
|
-
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;
|
|
623
540
|
}
|
|
624
541
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
infoLabel =
|
|
642
|
-
|
|
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];
|
|
643
561
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
[infoLabel setAlignment:NSTextAlignmentCenter];
|
|
653
|
-
[infoLabel setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightMedium]];
|
|
654
|
-
[infoLabel setTextColor:[NSColor whiteColor]];
|
|
655
|
-
|
|
656
|
-
// Force no borders on info label
|
|
657
|
-
[infoLabel setWantsLayer:YES];
|
|
658
|
-
infoLabel.layer.borderWidth = 0.0;
|
|
659
|
-
infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
660
|
-
infoLabel.layer.cornerRadius = 0.0;
|
|
661
|
-
infoLabel.layer.masksToBounds = YES;
|
|
662
|
-
|
|
663
|
-
[g_overlayWindow.contentView addSubview:infoLabel];
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Add/update app icon
|
|
667
|
-
NSImageView *appIconView = nil;
|
|
668
|
-
for (NSView *subview in [g_overlayWindow.contentView subviews]) {
|
|
669
|
-
if ([subview isKindOfClass:[NSImageView class]]) {
|
|
670
|
-
appIconView = (NSImageView*)subview;
|
|
671
|
-
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
|
+
}
|
|
672
570
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
appIconView.frame.size.width, appIconView.frame.size.height);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Get app icon using NSWorkspace
|
|
697
|
-
NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
|
|
698
|
-
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
|
699
|
-
NSArray *runningApps = [workspace runningApplications];
|
|
700
|
-
NSImage *appIcon = nil;
|
|
701
|
-
|
|
702
|
-
for (NSRunningApplication *app in runningApps) {
|
|
703
|
-
if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
|
|
704
|
-
appIcon = [app icon];
|
|
705
|
-
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);
|
|
706
591
|
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Fallback to generic app icon if not found
|
|
710
|
-
if (!appIcon) {
|
|
711
|
-
appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
|
|
712
|
-
NSLog(@"⚠️ Using fallback icon for app: %@", iconAppName);
|
|
713
|
-
} else {
|
|
714
|
-
NSLog(@"✅ Found app icon for: %@", iconAppName);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
[appIconView setImage:appIcon];
|
|
718
|
-
NSLog(@"🖼️ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
|
|
719
|
-
|
|
720
|
-
// Update label text
|
|
721
|
-
NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
|
|
722
|
-
NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
|
|
723
|
-
[infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
|
|
724
|
-
|
|
725
|
-
// Position buttons - Start Record in center, Cancel below it
|
|
726
|
-
if (g_selectButton) {
|
|
727
|
-
NSSize buttonSize = [g_selectButton frame].size;
|
|
728
|
-
NSPoint buttonCenter = NSMakePoint(
|
|
729
|
-
(width - buttonSize.width) / 2,
|
|
730
|
-
(height - buttonSize.height) / 2 + 15 // Slightly above center
|
|
731
|
-
);
|
|
732
|
-
[g_selectButton setFrameOrigin:buttonCenter];
|
|
733
592
|
|
|
734
|
-
//
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
[appIconView setFrameOrigin:iconCenter];
|
|
740
|
-
NSLog(@"🎯 Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
|
|
741
|
-
iconCenter.x, iconCenter.y, (float)width, (float)height);
|
|
742
|
-
|
|
743
|
-
// Add fast horizontal floating animation after positioning
|
|
744
|
-
[appIconView.layer removeAnimationForKey:@"floatAnimationX"];
|
|
745
|
-
[appIconView.layer removeAnimationForKey:@"floatAnimationY"];
|
|
746
|
-
|
|
747
|
-
// Faster horizontal float animation only
|
|
748
|
-
CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
|
|
749
|
-
floatAnimationX.fromValue = @(-4.0);
|
|
750
|
-
floatAnimationX.toValue = @(4.0);
|
|
751
|
-
floatAnimationX.duration = 1.0; // Much faster animation
|
|
752
|
-
floatAnimationX.repeatCount = HUGE_VALF;
|
|
753
|
-
floatAnimationX.autoreverses = YES;
|
|
754
|
-
floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
755
|
-
[appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
|
|
756
|
-
|
|
757
|
-
// Position info label at overlay center, above button
|
|
758
|
-
NSPoint labelCenter = NSMakePoint(
|
|
759
|
-
(width - [infoLabel frame].size.width) / 2, // Center horizontally
|
|
760
|
-
buttonCenter.y + buttonSize.height + 10 // 10px above button, below icon
|
|
761
|
-
);
|
|
762
|
-
[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;
|
|
763
598
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if ([subview isKindOfClass:[NSButton class]] &&
|
|
768
|
-
[[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
|
|
769
|
-
cancelButton = (NSButton*)subview;
|
|
599
|
+
for (NSRunningApplication *app in runningApps) {
|
|
600
|
+
if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
|
|
601
|
+
appIcon = [app icon];
|
|
770
602
|
break;
|
|
771
603
|
}
|
|
772
604
|
}
|
|
773
605
|
|
|
774
|
-
if
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
);
|
|
780
|
-
[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);
|
|
781
612
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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];
|
|
799
678
|
}
|
|
800
679
|
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
NSLog(@" ✅ Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
|
|
804
|
-
[g_overlayWindow level], [g_overlayWindow alphaValue],
|
|
805
|
-
[g_overlayWindow isVisible] ? "YES" : "NO");
|
|
806
|
-
|
|
807
|
-
// Update overlay view states - highlight window under cursor and check selection
|
|
808
|
-
if (g_overlayView && [g_overlayView isKindOfClass:[WindowSelectorOverlayView class]]) {
|
|
809
|
-
WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
|
|
810
680
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
681
|
+
[g_overlayWindow orderFront:nil];
|
|
682
|
+
[g_overlayWindow makeKeyAndOrderFront:nil];
|
|
683
|
+
|
|
684
|
+
// Ensure all subviews have no borders after positioning, but preserve corner radius for buttons and icons
|
|
685
|
+
for (NSView *subview in [g_overlayWindow.contentView subviews]) {
|
|
686
|
+
if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
|
|
687
|
+
[subview setWantsLayer:YES];
|
|
688
|
+
if (subview.layer) {
|
|
689
|
+
subview.layer.borderWidth = 0.0;
|
|
690
|
+
subview.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
691
|
+
subview.layer.masksToBounds = YES;
|
|
692
|
+
subview.layer.shadowOpacity = 0.0;
|
|
693
|
+
subview.layer.shadowRadius = 0.0;
|
|
694
|
+
subview.layer.shadowOffset = NSMakeSize(0, 0);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
814
698
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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";
|
|
818
706
|
|
|
819
|
-
|
|
820
|
-
overlayView.isActiveWindow = isUnderCursor; // Highlight if under cursor
|
|
821
|
-
overlayView.isSelectedWindow = isSelected; // Keep selection state
|
|
707
|
+
NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
|
|
822
708
|
|
|
823
|
-
|
|
824
|
-
|
|
709
|
+
[g_overlayWindow orderOut:nil];
|
|
710
|
+
[g_currentWindowUnderCursor release];
|
|
711
|
+
g_currentWindowUnderCursor = nil;
|
|
825
712
|
}
|
|
826
713
|
}
|
|
827
714
|
}
|
|
@@ -862,22 +749,9 @@ void cleanupWindowSelector() {
|
|
|
862
749
|
g_allWindows = nil;
|
|
863
750
|
}
|
|
864
751
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
g_selectedOverlayView = nil;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// Clear clicked window info (visual selection)
|
|
872
|
-
if (g_clickedWindowInfo) {
|
|
873
|
-
[g_clickedWindowInfo release];
|
|
874
|
-
g_clickedWindowInfo = nil;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Clear selected window info (recording selection)
|
|
878
|
-
if (g_selectedWindowInfo) {
|
|
879
|
-
[g_selectedWindowInfo release];
|
|
880
|
-
g_selectedWindowInfo = nil;
|
|
752
|
+
if (g_currentWindowUnderCursor) {
|
|
753
|
+
[g_currentWindowUnderCursor release];
|
|
754
|
+
g_currentWindowUnderCursor = nil;
|
|
881
755
|
}
|
|
882
756
|
}
|
|
883
757
|
|
|
@@ -1549,31 +1423,13 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1549
1423
|
|
|
1550
1424
|
@try {
|
|
1551
1425
|
// Get all windows
|
|
1552
|
-
g_allWindows = [
|
|
1426
|
+
g_allWindows = [getAllSelectableWindows() retain];
|
|
1553
1427
|
|
|
1554
1428
|
if (!g_allWindows || [g_allWindows count] == 0) {
|
|
1555
1429
|
Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
|
|
1556
1430
|
return env.Null();
|
|
1557
1431
|
}
|
|
1558
1432
|
|
|
1559
|
-
// Clear any previous selection
|
|
1560
|
-
if (g_selectedWindowInfo) {
|
|
1561
|
-
[g_selectedWindowInfo release];
|
|
1562
|
-
g_selectedWindowInfo = nil;
|
|
1563
|
-
}
|
|
1564
|
-
if (g_clickedWindowInfo) {
|
|
1565
|
-
[g_clickedWindowInfo release];
|
|
1566
|
-
g_clickedWindowInfo = nil;
|
|
1567
|
-
}
|
|
1568
|
-
if (g_selectedOverlayView) {
|
|
1569
|
-
g_selectedOverlayView.isSelectedWindow = NO;
|
|
1570
|
-
g_selectedOverlayView = nil;
|
|
1571
|
-
}
|
|
1572
|
-
if (g_currentWindowUnderCursor) {
|
|
1573
|
-
[g_currentWindowUnderCursor release];
|
|
1574
|
-
g_currentWindowUnderCursor = nil;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
1433
|
// Create overlay window (initially hidden)
|
|
1578
1434
|
NSRect initialFrame = NSMakeRect(0, 0, 100, 100);
|
|
1579
1435
|
g_overlayWindow = [[NSWindow alloc] initWithContentRect:initialFrame
|
|
@@ -1608,15 +1464,6 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1608
1464
|
g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:initialFrame];
|
|
1609
1465
|
[g_overlayWindow setContentView:g_overlayView];
|
|
1610
1466
|
|
|
1611
|
-
// Initialize overlay view properties
|
|
1612
|
-
[(WindowSelectorOverlayView *)g_overlayView setIsActiveWindow:NO];
|
|
1613
|
-
[(WindowSelectorOverlayView *)g_overlayView setIsSelectedWindow:NO];
|
|
1614
|
-
|
|
1615
|
-
// Set initial window under cursor for highlighting
|
|
1616
|
-
if (g_allWindows && [g_allWindows count] > 0) {
|
|
1617
|
-
g_currentWindowUnderCursor = [[g_allWindows objectAtIndex:0] retain];
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1467
|
// Note: NSWindow doesn't have setWantsLayer method, only NSView does
|
|
1621
1468
|
|
|
1622
1469
|
// Force content view to have no borders
|
|
@@ -1839,8 +1686,9 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
|
|
|
1839
1686
|
result.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
|
|
1840
1687
|
result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
|
|
1841
1688
|
|
|
1842
|
-
//
|
|
1843
|
-
|
|
1689
|
+
// Clear selected window info after reading
|
|
1690
|
+
[g_selectedWindowInfo release];
|
|
1691
|
+
g_selectedWindowInfo = nil;
|
|
1844
1692
|
|
|
1845
1693
|
return result;
|
|
1846
1694
|
|