node-mac-recorder 2.16.16 โ 2.16.18
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/.claude/settings.local.json +1 -1
- package/package.json +1 -1
- package/src/window_selector.mm +272 -251
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing
|
|
4
|
+
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing integrated overlay UI elements...'');\nconst WindowSelector = require(''./window-selector.js'');\nconst selector = new WindowSelector();\n\n// Test permission check first\nselector.checkPermissions().then(perms => {\n console.log(''Permissions:'', perms);\n \n // Start window selection to test integrated overlay elements\n return selector.startSelection();\n}).then(() => {\n console.log(''โ
Window selection started - UI elements should be integrated into overlay'');\n console.log(''โก๏ธ Move cursor over windows to test integrated button, logo, and title'');\n \n // Auto-stop after 15 seconds for testing\n setTimeout(() => {\n selector.stopSelection().then(() => {\n console.log(''โ
Window selection stopped - test complete'');\n });\n }, 15000);\n}).catch(console.error);\n\")"
|
|
5
5
|
],
|
|
6
6
|
"deny": [],
|
|
7
7
|
"ask": []
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -16,6 +16,15 @@ static NSTimer *g_trackingTimer = nil;
|
|
|
16
16
|
static NSDictionary *g_selectedWindowInfo = nil;
|
|
17
17
|
static NSMutableArray *g_allWindows = nil;
|
|
18
18
|
|
|
19
|
+
// Per-screen overlay system
|
|
20
|
+
static NSMutableArray *g_perScreenOverlays = nil;
|
|
21
|
+
static NSMutableArray *g_perScreenOverlayViews = nil;
|
|
22
|
+
static NSInteger g_activeScreenIndex = -1;
|
|
23
|
+
|
|
24
|
+
// Forward declaration for delegate
|
|
25
|
+
@class WindowSelectorDelegate;
|
|
26
|
+
static WindowSelectorDelegate *g_delegate = nil;
|
|
27
|
+
|
|
19
28
|
// Functions to hide/show main overlay window during recording
|
|
20
29
|
void hideAllOverlayWindows() {
|
|
21
30
|
if (g_overlayWindow && [g_overlayWindow isVisible]) {
|
|
@@ -68,6 +77,9 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo);
|
|
|
68
77
|
bool hideScreenRecordingPreview();
|
|
69
78
|
void updateScreenOverlays();
|
|
70
79
|
|
|
80
|
+
// Forward declarations
|
|
81
|
+
@class HoverButton;
|
|
82
|
+
|
|
71
83
|
// Custom overlay view class
|
|
72
84
|
@interface WindowSelectorOverlayView : NSView
|
|
73
85
|
@property (nonatomic, strong) NSDictionary *windowInfo;
|
|
@@ -75,7 +87,13 @@ void updateScreenOverlays();
|
|
|
75
87
|
@property (nonatomic) BOOL isToggled;
|
|
76
88
|
@property (nonatomic) NSRect highlightFrame;
|
|
77
89
|
@property (nonatomic, strong) NSValue *globalOriginOffset; // Store global coordinate offset
|
|
90
|
+
@property (nonatomic, strong) HoverButton *recordButton;
|
|
91
|
+
@property (nonatomic, strong) NSImageView *appIconView;
|
|
92
|
+
@property (nonatomic, strong) NSTextField *infoLabel;
|
|
78
93
|
- (void)setHighlightFrame:(NSRect)frame;
|
|
94
|
+
- (void)setupUIElements;
|
|
95
|
+
- (void)updateUIElements;
|
|
96
|
+
- (void)positionUIElements;
|
|
79
97
|
@end
|
|
80
98
|
|
|
81
99
|
// Custom button with hover effects
|
|
@@ -110,15 +128,14 @@ void updateScreenOverlays();
|
|
|
110
128
|
// Disable focus ring completely
|
|
111
129
|
[self setFocusRingType:NSFocusRingTypeNone];
|
|
112
130
|
|
|
113
|
-
//
|
|
131
|
+
// Setup UI elements (button, icon, label)
|
|
132
|
+
[self setupUIElements];
|
|
133
|
+
|
|
134
|
+
NSLog(@"๐ฏ WindowSelectorOverlayView created with UI elements integrated");
|
|
114
135
|
}
|
|
115
136
|
return self;
|
|
116
137
|
}
|
|
117
138
|
|
|
118
|
-
- (void)setHighlightFrame:(NSRect)frame {
|
|
119
|
-
_highlightFrame = frame;
|
|
120
|
-
[self setNeedsDisplay:YES]; // Trigger redraw
|
|
121
|
-
}
|
|
122
139
|
|
|
123
140
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
124
141
|
[super drawRect:dirtyRect];
|
|
@@ -213,6 +230,147 @@ void updateScreenOverlays();
|
|
|
213
230
|
NSLog(@"๐ฏ Global toggle state: %s", g_hasToggledWindow ? "HAS_TOGGLED" : "NO_TOGGLE");
|
|
214
231
|
}
|
|
215
232
|
|
|
233
|
+
- (void)setupUIElements {
|
|
234
|
+
NSLog(@"๐ง Setting up integrated UI elements");
|
|
235
|
+
|
|
236
|
+
// Create record button
|
|
237
|
+
self.recordButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 60)];
|
|
238
|
+
[self.recordButton setTitle:@"Start Recording"];
|
|
239
|
+
[self.recordButton setFont:[NSFont systemFontOfSize:18.0 weight:NSFontWeightMedium]];
|
|
240
|
+
[self.recordButton setBezelStyle:NSBezelStyleRounded];
|
|
241
|
+
[self.recordButton setTarget:g_delegate];
|
|
242
|
+
[self.recordButton setAction:@selector(selectButtonClicked:)];
|
|
243
|
+
|
|
244
|
+
// Style the button
|
|
245
|
+
self.recordButton.wantsLayer = YES;
|
|
246
|
+
self.recordButton.layer.backgroundColor = [[NSColor colorWithRed:0.2 green:0.6 blue:1.0 alpha:0.9] CGColor];
|
|
247
|
+
self.recordButton.layer.cornerRadius = 8.0;
|
|
248
|
+
self.recordButton.layer.borderWidth = 0.0;
|
|
249
|
+
|
|
250
|
+
[self addSubview:self.recordButton];
|
|
251
|
+
|
|
252
|
+
// Create app icon view
|
|
253
|
+
self.appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
|
|
254
|
+
[self.appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
|
|
255
|
+
[self.appIconView setWantsLayer:YES];
|
|
256
|
+
[self.appIconView.layer setCornerRadius:8.0];
|
|
257
|
+
[self.appIconView.layer setMasksToBounds:YES];
|
|
258
|
+
[self addSubview:self.appIconView];
|
|
259
|
+
|
|
260
|
+
// Create info label
|
|
261
|
+
self.infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 400, 60)];
|
|
262
|
+
[self.infoLabel setEditable:NO];
|
|
263
|
+
[self.infoLabel setSelectable:NO];
|
|
264
|
+
[self.infoLabel setBezeled:NO];
|
|
265
|
+
[self.infoLabel setDrawsBackground:NO];
|
|
266
|
+
[self.infoLabel setAlignment:NSTextAlignmentCenter];
|
|
267
|
+
[self.infoLabel setFont:[NSFont systemFontOfSize:23.4 weight:NSFontWeightMedium]];
|
|
268
|
+
[self.infoLabel setTextColor:[NSColor whiteColor]];
|
|
269
|
+
[self addSubview:self.infoLabel];
|
|
270
|
+
|
|
271
|
+
NSLog(@"โ
UI elements created and added to overlay view");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
- (void)updateUIElements {
|
|
275
|
+
if (!self.windowInfo) return;
|
|
276
|
+
|
|
277
|
+
NSLog(@"๐ Updating UI elements for window: %@", [self.windowInfo objectForKey:@"title"]);
|
|
278
|
+
|
|
279
|
+
// Update info label
|
|
280
|
+
NSString *windowTitle = [self.windowInfo objectForKey:@"title"] ?: @"Unknown Window";
|
|
281
|
+
NSString *appName = [self.windowInfo objectForKey:@"appName"] ?: @"Unknown App";
|
|
282
|
+
[self.infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", appName, windowTitle]];
|
|
283
|
+
|
|
284
|
+
// Update app icon
|
|
285
|
+
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
|
286
|
+
NSArray *runningApps = [workspace runningApplications];
|
|
287
|
+
NSImage *appIcon = nil;
|
|
288
|
+
|
|
289
|
+
for (NSRunningApplication *app in runningApps) {
|
|
290
|
+
if ([[app localizedName] isEqualToString:appName] || [[app bundleIdentifier] containsString:appName]) {
|
|
291
|
+
appIcon = [app icon];
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!appIcon) {
|
|
297
|
+
appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
[self.appIconView setImage:appIcon];
|
|
301
|
+
|
|
302
|
+
// Position elements
|
|
303
|
+
[self positionUIElements];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
- (void)positionUIElements {
|
|
307
|
+
if (NSEqualRects(self.highlightFrame, NSZeroRect)) {
|
|
308
|
+
// Hide elements when no window is highlighted
|
|
309
|
+
[self.recordButton setHidden:YES];
|
|
310
|
+
[self.appIconView setHidden:YES];
|
|
311
|
+
[self.infoLabel setHidden:YES];
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Show elements
|
|
316
|
+
[self.recordButton setHidden:NO];
|
|
317
|
+
[self.appIconView setHidden:NO];
|
|
318
|
+
[self.infoLabel setHidden:NO];
|
|
319
|
+
|
|
320
|
+
// Calculate window center
|
|
321
|
+
CGFloat windowCenterX = self.highlightFrame.origin.x + (self.highlightFrame.size.width / 2);
|
|
322
|
+
CGFloat windowCenterY = self.highlightFrame.origin.y + (self.highlightFrame.size.height / 2);
|
|
323
|
+
|
|
324
|
+
// Position record button at window center
|
|
325
|
+
NSSize buttonSize = self.recordButton.frame.size;
|
|
326
|
+
[self.recordButton setFrameOrigin:NSMakePoint(
|
|
327
|
+
windowCenterX - (buttonSize.width / 2),
|
|
328
|
+
windowCenterY - (buttonSize.height / 2)
|
|
329
|
+
)];
|
|
330
|
+
|
|
331
|
+
// Position app icon above window center
|
|
332
|
+
[self.appIconView setFrameOrigin:NSMakePoint(
|
|
333
|
+
windowCenterX - 48, // Center horizontally (96/2 = 48)
|
|
334
|
+
windowCenterY + 120 // 120px above window center
|
|
335
|
+
)];
|
|
336
|
+
|
|
337
|
+
// Add floating animation to app icon
|
|
338
|
+
[self.appIconView.layer removeAnimationForKey:@"floatAnimationX"];
|
|
339
|
+
CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
|
|
340
|
+
floatAnimationX.fromValue = @(-4.0);
|
|
341
|
+
floatAnimationX.toValue = @(4.0);
|
|
342
|
+
floatAnimationX.duration = 1.0;
|
|
343
|
+
floatAnimationX.repeatCount = HUGE_VALF;
|
|
344
|
+
floatAnimationX.autoreverses = YES;
|
|
345
|
+
floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
346
|
+
[self.appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
|
|
347
|
+
|
|
348
|
+
// Position info label between icon and button
|
|
349
|
+
NSSize labelSize = self.infoLabel.frame.size;
|
|
350
|
+
[self.infoLabel setFrameOrigin:NSMakePoint(
|
|
351
|
+
windowCenterX - (labelSize.width / 2),
|
|
352
|
+
windowCenterY + 50 // 50px above window center
|
|
353
|
+
)];
|
|
354
|
+
|
|
355
|
+
NSLog(@"๐ UI elements positioned - Button:(%.0f,%.0f) Icon:(%.0f,%.0f) Label:(%.0f,%.0f)",
|
|
356
|
+
self.recordButton.frame.origin.x, self.recordButton.frame.origin.y,
|
|
357
|
+
self.appIconView.frame.origin.x, self.appIconView.frame.origin.y,
|
|
358
|
+
self.infoLabel.frame.origin.x, self.infoLabel.frame.origin.y);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Override setHighlightFrame to trigger UI updates
|
|
362
|
+
- (void)setHighlightFrame:(NSRect)frame {
|
|
363
|
+
_highlightFrame = frame;
|
|
364
|
+
[self positionUIElements]; // Position UI elements when highlight changes
|
|
365
|
+
[self setNeedsDisplay:YES]; // Trigger redraw
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Override setWindowInfo to trigger UI updates
|
|
369
|
+
- (void)setWindowInfo:(NSDictionary *)windowInfo {
|
|
370
|
+
_windowInfo = windowInfo;
|
|
371
|
+
[self updateUIElements]; // Update UI elements when window info changes
|
|
372
|
+
}
|
|
373
|
+
|
|
216
374
|
// Layer-based approach, no custom drawing needed
|
|
217
375
|
|
|
218
376
|
@end
|
|
@@ -508,8 +666,6 @@ void updateScreenOverlays();
|
|
|
508
666
|
}
|
|
509
667
|
@end
|
|
510
668
|
|
|
511
|
-
static WindowSelectorDelegate *g_delegate = nil;
|
|
512
|
-
|
|
513
669
|
// Bring window to front using Accessibility API
|
|
514
670
|
bool bringWindowToFront(int windowId) {
|
|
515
671
|
@autoreleasepool {
|
|
@@ -866,242 +1022,74 @@ void updateOverlay() {
|
|
|
866
1022
|
}
|
|
867
1023
|
}
|
|
868
1024
|
|
|
869
|
-
//
|
|
870
|
-
// Account for multi-display offset from global origin
|
|
871
|
-
WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
|
|
872
|
-
NSPoint globalOffset = NSZeroPoint;
|
|
873
|
-
if (overlayView.globalOriginOffset) {
|
|
874
|
-
globalOffset = [overlayView.globalOriginOffset pointValue];
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Determine if this is a primary display window
|
|
1025
|
+
// NEW PER-SCREEN SYSTEM: Find which screen this window belongs to
|
|
878
1026
|
NSArray *allScreens = [NSScreen screens];
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
CGFloat localX, localY;
|
|
887
|
-
if (isPrimaryDisplayWindow) {
|
|
888
|
-
// Primary display windows: Calculate dynamic offset from combined frame
|
|
889
|
-
NSRect combinedFrame = [g_overlayWindow frame];
|
|
890
|
-
|
|
891
|
-
// Calculate primary screen offset within combined frame
|
|
892
|
-
CGFloat primaryOffsetX = primaryFrame.origin.x - combinedFrame.origin.x;
|
|
893
|
-
CGFloat primaryOffsetY = primaryFrame.origin.y - combinedFrame.origin.y;
|
|
894
|
-
|
|
895
|
-
NSLog(@"๐ง PRIMARY DEBUG:");
|
|
896
|
-
NSLog(@" Primary frame: (%.0f,%.0f) %.0fx%.0f", primaryFrame.origin.x, primaryFrame.origin.y, primaryFrame.size.width, primaryFrame.size.height);
|
|
897
|
-
NSLog(@" Combined frame: (%.0f,%.0f) %.0fx%.0f", combinedFrame.origin.x, combinedFrame.origin.y, combinedFrame.size.width, combinedFrame.size.height);
|
|
898
|
-
NSLog(@" Primary offset: (%.0f,%.0f)", primaryOffsetX, primaryOffsetY);
|
|
899
|
-
NSLog(@" Window coords: (%.0f,%.0f) โ Local: (%.0f,%.0f)", (CGFloat)x, (CGFloat)y, x + primaryOffsetX, ([g_overlayView frame].size.height - (y + primaryOffsetY)) - height);
|
|
900
|
-
|
|
901
|
-
localX = x + primaryOffsetX;
|
|
902
|
-
localY = ([g_overlayView frame].size.height - (y + primaryOffsetY)) - height;
|
|
1027
|
+
NSInteger targetScreenIndex = -1;
|
|
1028
|
+
NSScreen *targetScreen = nil;
|
|
1029
|
+
|
|
1030
|
+
// Find screen containing this window
|
|
1031
|
+
for (NSInteger i = 0; i < [allScreens count]; i++) {
|
|
1032
|
+
NSScreen *screen = [allScreens objectAtIndex:i];
|
|
1033
|
+
NSRect screenFrame = [screen frame];
|
|
903
1034
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
NSLog(@" GlobalOffset: (%.0f,%.0f)", globalOffset.x, globalOffset.y);
|
|
908
|
-
NSLog(@" Window coords: (%.0f,%.0f) โ Local: (%.0f,%.0f)", (CGFloat)x, (CGFloat)y, x - globalOffset.x, ([g_overlayView frame].size.height - (y - globalOffset.y)) - height);
|
|
1035
|
+
// Check if window center is within this screen bounds
|
|
1036
|
+
CGFloat windowCenterX = x + width/2;
|
|
1037
|
+
CGFloat windowCenterY = y + height/2;
|
|
909
1038
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
[overlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];
|
|
917
|
-
|
|
918
|
-
// Only reset toggle state when switching to different window (not for position updates)
|
|
919
|
-
if (!g_hasToggledWindow) {
|
|
920
|
-
[(WindowSelectorOverlayView *)g_overlayView setIsToggled:NO];
|
|
921
|
-
} else {
|
|
922
|
-
// Keep toggle state for locked window
|
|
923
|
-
[(WindowSelectorOverlayView *)g_overlayView setIsToggled:YES];
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Add/update info label above button
|
|
927
|
-
NSTextField *infoLabel = nil;
|
|
928
|
-
for (NSView *subview in [g_overlayWindow.contentView subviews]) {
|
|
929
|
-
if ([subview isKindOfClass:[NSTextField class]]) {
|
|
930
|
-
infoLabel = (NSTextField*)subview;
|
|
1039
|
+
if (windowCenterX >= screenFrame.origin.x &&
|
|
1040
|
+
windowCenterX <= screenFrame.origin.x + screenFrame.size.width &&
|
|
1041
|
+
windowCenterY >= screenFrame.origin.y &&
|
|
1042
|
+
windowCenterY <= screenFrame.origin.y + screenFrame.size.height) {
|
|
1043
|
+
targetScreenIndex = i;
|
|
1044
|
+
targetScreen = screen;
|
|
931
1045
|
break;
|
|
932
1046
|
}
|
|
933
1047
|
}
|
|
934
1048
|
|
|
935
|
-
if (
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
[infoLabel setSelectable:NO];
|
|
939
|
-
[infoLabel setBezeled:NO];
|
|
940
|
-
[infoLabel setDrawsBackground:NO];
|
|
941
|
-
[infoLabel setAlignment:NSTextAlignmentCenter];
|
|
942
|
-
[infoLabel setFont:[NSFont systemFontOfSize:23.4 weight:NSFontWeightMedium]]; // 18 * 1.3 = 23.4
|
|
943
|
-
[infoLabel setTextColor:[NSColor whiteColor]];
|
|
944
|
-
|
|
945
|
-
// Force no borders on info label
|
|
946
|
-
[infoLabel setWantsLayer:YES];
|
|
947
|
-
infoLabel.layer.borderWidth = 1.0;
|
|
948
|
-
infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
949
|
-
infoLabel.layer.cornerRadius = 0.0;
|
|
950
|
-
infoLabel.layer.masksToBounds = YES;
|
|
951
|
-
|
|
952
|
-
[g_overlayWindow.contentView addSubview:infoLabel];
|
|
1049
|
+
if (targetScreenIndex == -1) {
|
|
1050
|
+
NSLog(@"โ ๏ธ Window not found on any screen: (%.0f,%.0f)", (CGFloat)x, (CGFloat)y);
|
|
1051
|
+
return;
|
|
953
1052
|
}
|
|
954
1053
|
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
appIconView = (NSImageView*)subview;
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
1054
|
+
// Get the overlay for target screen
|
|
1055
|
+
NSWindow *targetOverlay = [g_perScreenOverlays objectAtIndex:targetScreenIndex];
|
|
1056
|
+
WindowSelectorOverlayView *targetOverlayView = [g_perScreenOverlayViews objectAtIndex:targetScreenIndex];
|
|
1057
|
+
NSRect targetScreenFrame = [targetScreen frame];
|
|
963
1058
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
[appIconView setWantsLayer:YES];
|
|
968
|
-
[appIconView.layer setCornerRadius:8.0];
|
|
969
|
-
[appIconView.layer setMasksToBounds:YES];
|
|
970
|
-
[appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
|
|
971
|
-
|
|
972
|
-
// Force no borders on app icon view
|
|
973
|
-
appIconView.layer.borderWidth = 1.0;
|
|
974
|
-
appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
975
|
-
appIconView.layer.shadowOpacity = 0.0;
|
|
976
|
-
appIconView.layer.shadowRadius = 0.0;
|
|
977
|
-
appIconView.layer.shadowOffset = NSMakeSize(0, 0);
|
|
978
|
-
|
|
979
|
-
[g_overlayWindow.contentView addSubview:appIconView];
|
|
980
|
-
NSLog(@"๐ผ๏ธ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
|
|
981
|
-
appIconView.frame.origin.x, appIconView.frame.origin.y,
|
|
982
|
-
appIconView.frame.size.width, appIconView.frame.size.height);
|
|
983
|
-
}
|
|
1059
|
+
// Calculate LOCAL coordinates within target screen (much simpler!)
|
|
1060
|
+
CGFloat localX = x - targetScreenFrame.origin.x;
|
|
1061
|
+
CGFloat localY = (targetScreenFrame.size.height - (y - targetScreenFrame.origin.y)) - height;
|
|
984
1062
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
|
988
|
-
NSArray *runningApps = [workspace runningApplications];
|
|
989
|
-
NSImage *appIcon = nil;
|
|
1063
|
+
NSLog(@"๐ฏ Window on Screen %ld: Global(%.0f,%.0f) โ Local(%.0f,%.0f)",
|
|
1064
|
+
targetScreenIndex, (CGFloat)x, (CGFloat)y, localX, localY);
|
|
990
1065
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1066
|
+
// Update active screen if changed
|
|
1067
|
+
if (g_activeScreenIndex != targetScreenIndex) {
|
|
1068
|
+
NSLog(@"๐ Switching to Screen %ld overlay", targetScreenIndex);
|
|
1069
|
+
g_activeScreenIndex = targetScreenIndex;
|
|
1070
|
+
g_overlayWindow = targetOverlay;
|
|
1071
|
+
g_overlayView = targetOverlayView;
|
|
996
1072
|
}
|
|
997
1073
|
|
|
998
|
-
//
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
NSLog(@"โ ๏ธ Using fallback icon for app: %@", iconAppName);
|
|
1002
|
-
} else {
|
|
1003
|
-
NSLog(@"โ
Found app icon for: %@", iconAppName);
|
|
1004
|
-
}
|
|
1074
|
+
// Update TARGET overlay view window info for highlighting
|
|
1075
|
+
[targetOverlayView setWindowInfo:targetWindow];
|
|
1076
|
+
[targetOverlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];
|
|
1005
1077
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
[infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
|
|
1013
|
-
|
|
1014
|
-
// Position buttons - Start Record in center of selected window
|
|
1015
|
-
if (g_selectButton) {
|
|
1016
|
-
NSSize buttonSize = [g_selectButton frame].size;
|
|
1017
|
-
// Use local window center for positioning
|
|
1018
|
-
CGFloat localWindowCenterX = localX + (width / 2);
|
|
1019
|
-
CGFloat localWindowCenterY = localY + (height / 2);
|
|
1020
|
-
NSPoint buttonCenter = NSMakePoint(
|
|
1021
|
-
localWindowCenterX - (buttonSize.width / 2),
|
|
1022
|
-
localWindowCenterY - (buttonSize.height / 2) // Perfect center of window
|
|
1023
|
-
);
|
|
1024
|
-
|
|
1025
|
-
NSLog(@" ButtonCalc: WindowCenter(%.0f, %.0f) -> Button(%.0f, %.0f)",
|
|
1026
|
-
localWindowCenterX, localWindowCenterY, buttonCenter.x, buttonCenter.y);
|
|
1027
|
-
|
|
1028
|
-
[g_selectButton setFrameOrigin:buttonCenter];
|
|
1029
|
-
|
|
1030
|
-
// Position app icon above window center
|
|
1031
|
-
NSPoint iconCenter = NSMakePoint(
|
|
1032
|
-
localWindowCenterX - (96 / 2), // Center horizontally on window
|
|
1033
|
-
localWindowCenterY + 120 // 120px above window center
|
|
1034
|
-
);
|
|
1035
|
-
[appIconView setFrameOrigin:iconCenter];
|
|
1036
|
-
NSLog(@"๐ฏ Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
|
|
1037
|
-
iconCenter.x, iconCenter.y, (float)width, (float)height);
|
|
1038
|
-
|
|
1039
|
-
// Add fast horizontal floating animation after positioning
|
|
1040
|
-
[appIconView.layer removeAnimationForKey:@"floatAnimationX"];
|
|
1041
|
-
[appIconView.layer removeAnimationForKey:@"floatAnimationY"];
|
|
1042
|
-
|
|
1043
|
-
// Faster horizontal float animation only
|
|
1044
|
-
CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
|
|
1045
|
-
floatAnimationX.fromValue = @(-4.0);
|
|
1046
|
-
floatAnimationX.toValue = @(4.0);
|
|
1047
|
-
floatAnimationX.duration = 1.0; // Much faster animation
|
|
1048
|
-
floatAnimationX.repeatCount = HUGE_VALF;
|
|
1049
|
-
floatAnimationX.autoreverses = YES;
|
|
1050
|
-
floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
1051
|
-
[appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
|
|
1052
|
-
|
|
1053
|
-
// Position info label between icon and button relative to window center
|
|
1054
|
-
NSPoint labelCenter = NSMakePoint(
|
|
1055
|
-
localWindowCenterX - ([infoLabel frame].size.width / 2), // Center horizontally on window
|
|
1056
|
-
localWindowCenterY + 50 // 50px above window center, below icon
|
|
1057
|
-
);
|
|
1058
|
-
[infoLabel setFrameOrigin:labelCenter];
|
|
1059
|
-
|
|
1060
|
-
// Position cancel button below the main button
|
|
1061
|
-
NSButton *cancelButton = nil;
|
|
1062
|
-
for (NSView *subview in [g_overlayWindow.contentView subviews]) {
|
|
1063
|
-
if ([subview isKindOfClass:[NSButton class]] &&
|
|
1064
|
-
[[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
|
|
1065
|
-
cancelButton = (NSButton*)subview;
|
|
1066
|
-
break;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
if (cancelButton) {
|
|
1071
|
-
NSSize cancelButtonSize = [cancelButton frame].size;
|
|
1072
|
-
NSPoint cancelButtonCenter = NSMakePoint(
|
|
1073
|
-
localWindowCenterX - (cancelButtonSize.width / 2), // Center horizontally on window
|
|
1074
|
-
localWindowCenterY - 80 // 80px below window center
|
|
1075
|
-
);
|
|
1076
|
-
[cancelButton setFrameOrigin:cancelButtonCenter];
|
|
1077
|
-
}
|
|
1078
|
+
// Only reset toggle state when switching to different window (not for position updates)
|
|
1079
|
+
if (!g_hasToggledWindow) {
|
|
1080
|
+
[targetOverlayView setIsToggled:NO];
|
|
1081
|
+
} else {
|
|
1082
|
+
// Keep toggle state for locked window
|
|
1083
|
+
[targetOverlayView setIsToggled:YES];
|
|
1078
1084
|
}
|
|
1079
1085
|
|
|
1080
|
-
|
|
1081
|
-
// DON'T make key - prevents focus ring
|
|
1082
|
-
// [g_overlayWindow makeKeyAndOrderFront:nil];
|
|
1086
|
+
NSLog(@"โ
Updated overlay view with integrated UI elements");
|
|
1083
1087
|
|
|
1084
|
-
|
|
1085
|
-
for (NSView *subview in [g_overlayWindow.contentView subviews]) {
|
|
1086
|
-
// Skip the main overlay view - it handles its own borders
|
|
1087
|
-
if ([subview isKindOfClass:[WindowSelectorOverlayView class]]) continue;
|
|
1088
|
-
|
|
1089
|
-
if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
|
|
1090
|
-
[subview setWantsLayer:YES];
|
|
1091
|
-
if (subview.layer) {
|
|
1092
|
-
subview.layer.borderWidth = 1.0;
|
|
1093
|
-
subview.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
1094
|
-
subview.layer.masksToBounds = YES;
|
|
1095
|
-
subview.layer.shadowOpacity = 0.0;
|
|
1096
|
-
subview.layer.shadowRadius = 0.0;
|
|
1097
|
-
subview.layer.shadowOffset = NSMakeSize(0, 0);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1088
|
+
[targetOverlay orderFront:nil];
|
|
1101
1089
|
|
|
1102
|
-
NSLog(@" โ
Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s
|
|
1103
|
-
[
|
|
1104
|
-
[
|
|
1090
|
+
NSLog(@" โ
Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s",
|
|
1091
|
+
[targetOverlay level], [targetOverlay alphaValue],
|
|
1092
|
+
[targetOverlay isVisible] ? "YES" : "NO");
|
|
1105
1093
|
} else if (!windowUnderCursor && g_currentWindowUnderCursor) {
|
|
1106
1094
|
// No window under cursor and no toggle active, hide overlay
|
|
1107
1095
|
NSString *leftWindowTitle = [g_currentWindowUnderCursor objectForKey:@"title"] ?: @"Untitled";
|
|
@@ -1133,7 +1121,27 @@ void cleanupWindowSelector() {
|
|
|
1133
1121
|
g_windowKeyEventMonitor = nil;
|
|
1134
1122
|
}
|
|
1135
1123
|
|
|
1136
|
-
// Close
|
|
1124
|
+
// NEW PER-SCREEN CLEANUP: Close all per-screen overlays
|
|
1125
|
+
if (g_perScreenOverlays) {
|
|
1126
|
+
NSLog(@"๐งน Cleaning up %lu per-screen overlays", [g_perScreenOverlays count]);
|
|
1127
|
+
for (NSWindow *overlay in g_perScreenOverlays) {
|
|
1128
|
+
if (overlay) {
|
|
1129
|
+
[overlay close];
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
[g_perScreenOverlays release];
|
|
1133
|
+
g_perScreenOverlays = nil;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (g_perScreenOverlayViews) {
|
|
1137
|
+
[g_perScreenOverlayViews release];
|
|
1138
|
+
g_perScreenOverlayViews = nil;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Reset per-screen state
|
|
1142
|
+
g_activeScreenIndex = -1;
|
|
1143
|
+
|
|
1144
|
+
// Close legacy overlay window (fallback cleanup)
|
|
1137
1145
|
if (g_overlayWindow) {
|
|
1138
1146
|
[g_overlayWindow close];
|
|
1139
1147
|
g_overlayWindow = nil;
|
|
@@ -1846,32 +1854,55 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1846
1854
|
}
|
|
1847
1855
|
|
|
1848
1856
|
// Get all windows
|
|
1849
|
-
g_allWindows = [getAllSelectableWindows()
|
|
1857
|
+
g_allWindows = [[NSMutableArray alloc] initWithArray:getAllSelectableWindows()];
|
|
1850
1858
|
|
|
1851
1859
|
if (!g_allWindows || [g_allWindows count] == 0) {
|
|
1852
1860
|
Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
|
|
1853
1861
|
return env.Null();
|
|
1854
1862
|
}
|
|
1855
1863
|
|
|
1856
|
-
// Create
|
|
1864
|
+
// Create PER-SCREEN overlay windows (much simpler than combined overlay)
|
|
1857
1865
|
NSArray *allScreens = [NSScreen screens];
|
|
1858
|
-
|
|
1866
|
+
g_perScreenOverlays = [[NSMutableArray alloc] init];
|
|
1867
|
+
g_perScreenOverlayViews = [[NSMutableArray alloc] init];
|
|
1868
|
+
|
|
1869
|
+
NSLog(@"๐ฅ๏ธ Creating per-screen overlays for %lu displays", [allScreens count]);
|
|
1859
1870
|
|
|
1860
|
-
//
|
|
1861
|
-
for (
|
|
1871
|
+
// Create overlay for each screen
|
|
1872
|
+
for (NSInteger i = 0; i < [allScreens count]; i++) {
|
|
1873
|
+
NSScreen *screen = [allScreens objectAtIndex:i];
|
|
1862
1874
|
NSRect screenFrame = [screen frame];
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1875
|
+
|
|
1876
|
+
// Create per-screen overlay window
|
|
1877
|
+
NSWindow *screenOverlay = [[NoFocusWindow alloc] initWithContentRect:screenFrame
|
|
1878
|
+
styleMask:NSWindowStyleMaskBorderless
|
|
1879
|
+
backing:NSBackingStoreBuffered
|
|
1880
|
+
defer:NO
|
|
1881
|
+
screen:screen];
|
|
1882
|
+
|
|
1883
|
+
// Create per-screen overlay view
|
|
1884
|
+
WindowSelectorOverlayView *overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:NSMakeRect(0, 0, screenFrame.size.width, screenFrame.size.height)];
|
|
1885
|
+
[screenOverlay setContentView:overlayView];
|
|
1886
|
+
|
|
1887
|
+
// Configure overlay window
|
|
1888
|
+
[screenOverlay setStyleMask:NSWindowStyleMaskBorderless];
|
|
1889
|
+
[screenOverlay setBackgroundColor:[NSColor clearColor]];
|
|
1890
|
+
[screenOverlay setOpaque:NO];
|
|
1891
|
+
[screenOverlay setIgnoresMouseEvents:NO];
|
|
1892
|
+
[screenOverlay setAcceptsMouseMovedEvents:YES];
|
|
1893
|
+
[screenOverlay setLevel:NSScreenSaverWindowLevel];
|
|
1894
|
+
[screenOverlay setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary];
|
|
1895
|
+
|
|
1896
|
+
NSLog(@" Screen %ld: (%.0f,%.0f) %.0fx%.0f โ Overlay created", i, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
|
|
1897
|
+
|
|
1898
|
+
[g_perScreenOverlays addObject:screenOverlay];
|
|
1899
|
+
[g_perScreenOverlayViews addObject:overlayView];
|
|
1868
1900
|
}
|
|
1869
1901
|
|
|
1870
|
-
|
|
1871
|
-
g_overlayWindow = [
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
defer:NO];
|
|
1902
|
+
// For compatibility, keep first screen as main overlay
|
|
1903
|
+
g_overlayWindow = [g_perScreenOverlays objectAtIndex:0];
|
|
1904
|
+
g_overlayView = [g_perScreenOverlayViews objectAtIndex:0];
|
|
1905
|
+
g_activeScreenIndex = 0;
|
|
1875
1906
|
|
|
1876
1907
|
// Force completely borderless appearance
|
|
1877
1908
|
[g_overlayWindow setStyleMask:NSWindowStyleMaskBorderless];
|
|
@@ -1896,17 +1927,7 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1896
1927
|
[g_overlayWindow setOpaque:NO];
|
|
1897
1928
|
[g_overlayWindow setBackgroundColor:[NSColor clearColor]];
|
|
1898
1929
|
|
|
1899
|
-
|
|
1900
|
-
// Convert global coordinates to local view coordinates (offset by origin)
|
|
1901
|
-
NSRect localViewFrame = NSMakeRect(0, 0, fullScreenFrame.size.width, fullScreenFrame.size.height);
|
|
1902
|
-
g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:localViewFrame];
|
|
1903
|
-
|
|
1904
|
-
// Store the global origin offset for coordinate conversion
|
|
1905
|
-
WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
|
|
1906
|
-
overlayView.globalOriginOffset = [NSValue valueWithPoint:fullScreenFrame.origin];
|
|
1907
|
-
|
|
1908
|
-
NSLog(@"๐ง SET GlobalOriginOffset: (%.0f, %.0f)", fullScreenFrame.origin.x, fullScreenFrame.origin.y);
|
|
1909
|
-
[g_overlayWindow setContentView:g_overlayView];
|
|
1930
|
+
NSLog(@"โ
Per-screen overlay system initialized with %lu screens", [allScreens count]);
|
|
1910
1931
|
|
|
1911
1932
|
// Note: NSWindow doesn't have setWantsLayer method, only NSView does
|
|
1912
1933
|
|