node-mac-recorder 2.16.19 โ†’ 2.16.20

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing UI elements on ALL screens...'');\nconst WindowSelector = require(''./window-selector.js'');\nconst selector = new WindowSelector();\n\nselector.checkPermissions().then(perms => {\n console.log(''Permissions:'', perms);\n return selector.startSelection();\n}).then(() => {\n console.log(''โœ… Window selection started - TEST UI elements on BOTH screens'');\n console.log(''๐Ÿ–ฅ๏ธ Primary display: Move cursor over windows'');\n console.log(''๐Ÿ–ฅ๏ธ External display: Move cursor over windows'');\n console.log(''โžก๏ธ Check if Record/Cancel buttons + logo + title appear on BOTH displays'');\n \n setTimeout(() => {\n selector.stopSelection().then(() => {\n console.log(''โœ… Test complete'');\n });\n }, 20000);\n}).catch(console.error);\n\")"
4
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing multi-screen window selector UI positioning fix...'');\nconst WindowSelector = require(''./window-selector.js'');\nconst selector = new WindowSelector();\n\nselector.checkPermissions().then(perms => {\n console.log(''Permissions:'', perms);\n return selector.startSelection();\n}).then(() => {\n console.log(''โœ… Window selection started - CRITICAL TEST:'');\n console.log(''๐Ÿ–ฅ๏ธ PRIMARY DISPLAY: Move cursor over windows and verify Record/Cancel buttons + logo + title appear'');\n console.log(''๐Ÿ–ฅ๏ธ EXTERNAL DISPLAY: Move cursor over windows on second screen'');\n console.log(''๐Ÿ“ VERIFY: UI elements appear on the SAME screen as the window (not just primary)'');\n console.log(''โšก This fixes the multi-screen UI positioning bug!'');\n \n setTimeout(() => {\n selector.stopSelection().then(() => {\n console.log(''โœ… Multi-screen UI positioning test complete'');\n });\n }, 20000);\n}).catch(console.error);\")"
5
5
  ],
6
6
  "deny": [],
7
7
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.19",
3
+ "version": "2.16.20",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -21,10 +21,6 @@ static NSMutableArray *g_perScreenOverlays = nil;
21
21
  static NSMutableArray *g_perScreenOverlayViews = nil;
22
22
  static NSInteger g_activeScreenIndex = -1;
23
23
 
24
- // Forward declaration for delegate
25
- @class WindowSelectorDelegate;
26
- static WindowSelectorDelegate *g_delegate = nil;
27
-
28
24
  // Functions to hide/show main overlay window during recording
29
25
  void hideAllOverlayWindows() {
30
26
  if (g_overlayWindow && [g_overlayWindow isVisible]) {
@@ -77,9 +73,6 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo);
77
73
  bool hideScreenRecordingPreview();
78
74
  void updateScreenOverlays();
79
75
 
80
- // Forward declarations
81
- @class HoverButton;
82
-
83
76
  // Custom overlay view class
84
77
  @interface WindowSelectorOverlayView : NSView
85
78
  @property (nonatomic, strong) NSDictionary *windowInfo;
@@ -87,14 +80,7 @@ void updateScreenOverlays();
87
80
  @property (nonatomic) BOOL isToggled;
88
81
  @property (nonatomic) NSRect highlightFrame;
89
82
  @property (nonatomic, strong) NSValue *globalOriginOffset; // Store global coordinate offset
90
- @property (nonatomic, strong) HoverButton *recordButton;
91
- @property (nonatomic, strong) HoverButton *cancelButton;
92
- @property (nonatomic, strong) NSImageView *appIconView;
93
- @property (nonatomic, strong) NSTextField *infoLabel;
94
83
  - (void)setHighlightFrame:(NSRect)frame;
95
- - (void)setupUIElements;
96
- - (void)updateUIElements;
97
- - (void)positionUIElements;
98
84
  @end
99
85
 
100
86
  // Custom button with hover effects
@@ -129,14 +115,15 @@ void updateScreenOverlays();
129
115
  // Disable focus ring completely
130
116
  [self setFocusRingType:NSFocusRingTypeNone];
131
117
 
132
- // Setup UI elements (button, icon, label)
133
- [self setupUIElements];
134
-
135
- NSLog(@"๐ŸŽฏ WindowSelectorOverlayView created with UI elements integrated");
118
+ // Window selector overlay view created
136
119
  }
137
120
  return self;
138
121
  }
139
122
 
123
+ - (void)setHighlightFrame:(NSRect)frame {
124
+ _highlightFrame = frame;
125
+ [self setNeedsDisplay:YES]; // Trigger redraw
126
+ }
140
127
 
141
128
  - (void)drawRect:(NSRect)dirtyRect {
142
129
  [super drawRect:dirtyRect];
@@ -231,178 +218,6 @@ void updateScreenOverlays();
231
218
  NSLog(@"๐ŸŽฏ Global toggle state: %s", g_hasToggledWindow ? "HAS_TOGGLED" : "NO_TOGGLE");
232
219
  }
233
220
 
234
- - (void)setupUIElements {
235
- NSLog(@"๐Ÿ”ง Setting up integrated UI elements");
236
-
237
- // Ensure delegate exists
238
- if (!g_delegate) {
239
- NSLog(@"โš ๏ธ No delegate found in setupUIElements, this may cause button click issues");
240
- }
241
-
242
- // Create record button
243
- self.recordButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 60)];
244
- [self.recordButton setTitle:@"Start Recording"];
245
- [self.recordButton setFont:[NSFont systemFontOfSize:18.0 weight:NSFontWeightMedium]];
246
- [self.recordButton setBezelStyle:NSBezelStyleRounded];
247
- [self.recordButton setTarget:g_delegate];
248
- [self.recordButton setAction:@selector(selectButtonClicked:)];
249
-
250
- // Style the button
251
- self.recordButton.wantsLayer = YES;
252
- self.recordButton.layer.backgroundColor = [[NSColor colorWithRed:0.2 green:0.6 blue:1.0 alpha:0.9] CGColor];
253
- self.recordButton.layer.cornerRadius = 8.0;
254
- self.recordButton.layer.borderWidth = 0.0;
255
-
256
- [self addSubview:self.recordButton];
257
-
258
- // Create cancel button
259
- self.cancelButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
260
- [self.cancelButton setTitle:@"Cancel"];
261
- [self.cancelButton setFont:[NSFont systemFontOfSize:14.0 weight:NSFontWeightMedium]];
262
- [self.cancelButton setBezelStyle:NSBezelStyleRounded];
263
- [self.cancelButton setTarget:g_delegate];
264
- [self.cancelButton setAction:@selector(cancelButtonClicked:)];
265
-
266
- // Style the cancel button
267
- self.cancelButton.wantsLayer = YES;
268
- self.cancelButton.layer.backgroundColor = [[NSColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:0.9] CGColor];
269
- self.cancelButton.layer.cornerRadius = 6.0;
270
- self.cancelButton.layer.borderWidth = 0.0;
271
-
272
- [self addSubview:self.cancelButton];
273
-
274
- // Create app icon view
275
- self.appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
276
- [self.appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
277
- [self.appIconView setWantsLayer:YES];
278
- [self.appIconView.layer setCornerRadius:8.0];
279
- [self.appIconView.layer setMasksToBounds:YES];
280
- [self addSubview:self.appIconView];
281
-
282
- // Create info label
283
- self.infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 400, 60)];
284
- [self.infoLabel setEditable:NO];
285
- [self.infoLabel setSelectable:NO];
286
- [self.infoLabel setBezeled:NO];
287
- [self.infoLabel setDrawsBackground:NO];
288
- [self.infoLabel setAlignment:NSTextAlignmentCenter];
289
- [self.infoLabel setFont:[NSFont systemFontOfSize:23.4 weight:NSFontWeightMedium]];
290
- [self.infoLabel setTextColor:[NSColor whiteColor]];
291
- [self addSubview:self.infoLabel];
292
-
293
- NSLog(@"โœ… UI elements created and added to overlay view");
294
- }
295
-
296
- - (void)updateUIElements {
297
- if (!self.windowInfo) return;
298
-
299
- NSLog(@"๐Ÿ”„ Updating UI elements for window: %@", [self.windowInfo objectForKey:@"title"]);
300
-
301
- // Update info label
302
- NSString *windowTitle = [self.windowInfo objectForKey:@"title"] ?: @"Unknown Window";
303
- NSString *appName = [self.windowInfo objectForKey:@"appName"] ?: @"Unknown App";
304
- [self.infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", appName, windowTitle]];
305
-
306
- // Update app icon
307
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
308
- NSArray *runningApps = [workspace runningApplications];
309
- NSImage *appIcon = nil;
310
-
311
- for (NSRunningApplication *app in runningApps) {
312
- if ([[app localizedName] isEqualToString:appName] || [[app bundleIdentifier] containsString:appName]) {
313
- appIcon = [app icon];
314
- break;
315
- }
316
- }
317
-
318
- if (!appIcon) {
319
- appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
320
- }
321
-
322
- [self.appIconView setImage:appIcon];
323
-
324
- // Position elements
325
- [self positionUIElements];
326
- }
327
-
328
- - (void)positionUIElements {
329
- if (NSEqualRects(self.highlightFrame, NSZeroRect)) {
330
- // Hide elements when no window is highlighted
331
- [self.recordButton setHidden:YES];
332
- [self.cancelButton setHidden:YES];
333
- [self.appIconView setHidden:YES];
334
- [self.infoLabel setHidden:YES];
335
- return;
336
- }
337
-
338
- // Show elements
339
- [self.recordButton setHidden:NO];
340
- [self.cancelButton setHidden:NO];
341
- [self.appIconView setHidden:NO];
342
- [self.infoLabel setHidden:NO];
343
-
344
- // Calculate window center
345
- CGFloat windowCenterX = self.highlightFrame.origin.x + (self.highlightFrame.size.width / 2);
346
- CGFloat windowCenterY = self.highlightFrame.origin.y + (self.highlightFrame.size.height / 2);
347
-
348
- // Position record button at window center
349
- NSSize buttonSize = self.recordButton.frame.size;
350
- [self.recordButton setFrameOrigin:NSMakePoint(
351
- windowCenterX - (buttonSize.width / 2),
352
- windowCenterY - (buttonSize.height / 2)
353
- )];
354
-
355
- // Position app icon above window center
356
- [self.appIconView setFrameOrigin:NSMakePoint(
357
- windowCenterX - 48, // Center horizontally (96/2 = 48)
358
- windowCenterY + 120 // 120px above window center
359
- )];
360
-
361
- // Add floating animation to app icon
362
- [self.appIconView.layer removeAnimationForKey:@"floatAnimationX"];
363
- CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
364
- floatAnimationX.fromValue = @(-4.0);
365
- floatAnimationX.toValue = @(4.0);
366
- floatAnimationX.duration = 1.0;
367
- floatAnimationX.repeatCount = HUGE_VALF;
368
- floatAnimationX.autoreverses = YES;
369
- floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
370
- [self.appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
371
-
372
- // Position info label between icon and button
373
- NSSize labelSize = self.infoLabel.frame.size;
374
- [self.infoLabel setFrameOrigin:NSMakePoint(
375
- windowCenterX - (labelSize.width / 2),
376
- windowCenterY + 50 // 50px above window center
377
- )];
378
-
379
- // Position cancel button below record button
380
- NSSize cancelButtonSize = self.cancelButton.frame.size;
381
- [self.cancelButton setFrameOrigin:NSMakePoint(
382
- windowCenterX - (cancelButtonSize.width / 2),
383
- windowCenterY - 80 // 80px below window center
384
- )];
385
-
386
- NSLog(@"๐Ÿ“ UI elements positioned - Button:(%.0f,%.0f) Cancel:(%.0f,%.0f) Icon:(%.0f,%.0f) Label:(%.0f,%.0f)",
387
- self.recordButton.frame.origin.x, self.recordButton.frame.origin.y,
388
- self.cancelButton.frame.origin.x, self.cancelButton.frame.origin.y,
389
- self.appIconView.frame.origin.x, self.appIconView.frame.origin.y,
390
- self.infoLabel.frame.origin.x, self.infoLabel.frame.origin.y);
391
- }
392
-
393
- // Override setHighlightFrame to trigger UI updates
394
- - (void)setHighlightFrame:(NSRect)frame {
395
- _highlightFrame = frame;
396
- [self positionUIElements]; // Position UI elements when highlight changes
397
- [self setNeedsDisplay:YES]; // Trigger redraw
398
- }
399
-
400
- // Override setWindowInfo to trigger UI updates
401
- - (void)setWindowInfo:(NSDictionary *)windowInfo {
402
- _windowInfo = windowInfo;
403
- [self updateUIElements]; // Update UI elements when window info changes
404
- }
405
-
406
221
  // Layer-based approach, no custom drawing needed
407
222
 
408
223
  @end
@@ -698,6 +513,8 @@ void updateScreenOverlays();
698
513
  }
699
514
  @end
700
515
 
516
+ static WindowSelectorDelegate *g_delegate = nil;
517
+
701
518
  // Bring window to front using Accessibility API
702
519
  bool bringWindowToFront(int windowId) {
703
520
  @autoreleasepool {
@@ -1109,19 +926,276 @@ void updateOverlay() {
1109
926
 
1110
927
  // Only reset toggle state when switching to different window (not for position updates)
1111
928
  if (!g_hasToggledWindow) {
1112
- [targetOverlayView setIsToggled:NO];
929
+ [(WindowSelectorOverlayView *)g_overlayView setIsToggled:NO];
1113
930
  } else {
1114
931
  // Keep toggle state for locked window
1115
- [targetOverlayView setIsToggled:YES];
932
+ [(WindowSelectorOverlayView *)g_overlayView setIsToggled:YES];
933
+ }
934
+
935
+ // CRITICAL FIX: Use targetOverlay (correct screen) instead of g_overlayWindow
936
+ // Add/update info label above button on the TARGET screen
937
+ NSTextField *infoLabel = nil;
938
+ for (NSView *subview in [targetOverlay.contentView subviews]) {
939
+ if ([subview isKindOfClass:[NSTextField class]]) {
940
+ infoLabel = (NSTextField*)subview;
941
+ break;
942
+ }
943
+ }
944
+
945
+ if (!infoLabel) {
946
+ infoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, width - 40, 60)];
947
+ [infoLabel setEditable:NO];
948
+ [infoLabel setSelectable:NO];
949
+ [infoLabel setBezeled:NO];
950
+ [infoLabel setDrawsBackground:NO];
951
+ [infoLabel setAlignment:NSTextAlignmentCenter];
952
+ [infoLabel setFont:[NSFont systemFontOfSize:23.4 weight:NSFontWeightMedium]]; // 18 * 1.3 = 23.4
953
+ [infoLabel setTextColor:[NSColor whiteColor]];
954
+
955
+ // Force no borders on info label
956
+ [infoLabel setWantsLayer:YES];
957
+ infoLabel.layer.borderWidth = 1.0;
958
+ infoLabel.layer.borderColor = [[NSColor clearColor] CGColor];
959
+ infoLabel.layer.cornerRadius = 0.0;
960
+ infoLabel.layer.masksToBounds = YES;
961
+
962
+ [targetOverlay.contentView addSubview:infoLabel];
963
+ }
964
+
965
+ // CRITICAL FIX: Add/update app icon on the TARGET screen
966
+ NSImageView *appIconView = nil;
967
+ for (NSView *subview in [targetOverlay.contentView subviews]) {
968
+ if ([subview isKindOfClass:[NSImageView class]]) {
969
+ appIconView = (NSImageView*)subview;
970
+ break;
971
+ }
972
+ }
973
+
974
+ if (!appIconView) {
975
+ appIconView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 96, 96)];
976
+ [appIconView setImageScaling:NSImageScaleProportionallyUpOrDown];
977
+ [appIconView setWantsLayer:YES];
978
+ [appIconView.layer setCornerRadius:8.0];
979
+ [appIconView.layer setMasksToBounds:YES];
980
+ [appIconView.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.3] CGColor]]; // Debug background
981
+
982
+ // Force no borders on app icon view
983
+ appIconView.layer.borderWidth = 1.0;
984
+ appIconView.layer.borderColor = [[NSColor clearColor] CGColor];
985
+ appIconView.layer.shadowOpacity = 0.0;
986
+ appIconView.layer.shadowRadius = 0.0;
987
+ appIconView.layer.shadowOffset = NSMakeSize(0, 0);
988
+
989
+ [targetOverlay.contentView addSubview:appIconView];
990
+ NSLog(@"๐Ÿ–ผ๏ธ Created app icon view at frame: (%.0f, %.0f, %.0f, %.0f)",
991
+ appIconView.frame.origin.x, appIconView.frame.origin.y,
992
+ appIconView.frame.size.width, appIconView.frame.size.height);
993
+ }
994
+
995
+ // Get app icon using NSWorkspace
996
+ NSString *iconAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
997
+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
998
+ NSArray *runningApps = [workspace runningApplications];
999
+ NSImage *appIcon = nil;
1000
+
1001
+ for (NSRunningApplication *app in runningApps) {
1002
+ if ([[app localizedName] isEqualToString:iconAppName] || [[app bundleIdentifier] containsString:iconAppName]) {
1003
+ appIcon = [app icon];
1004
+ break;
1005
+ }
1116
1006
  }
1117
1007
 
1118
- NSLog(@"โœ… Updated overlay view with integrated UI elements");
1008
+ // Fallback to generic app icon if not found
1009
+ if (!appIcon) {
1010
+ appIcon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
1011
+ NSLog(@"โš ๏ธ Using fallback icon for app: %@", iconAppName);
1012
+ } else {
1013
+ NSLog(@"โœ… Found app icon for: %@", iconAppName);
1014
+ }
1015
+
1016
+ [appIconView setImage:appIcon];
1017
+ NSLog(@"๐Ÿ–ผ๏ธ Set icon image, size: %.0fx%.0f", [appIcon size].width, [appIcon size].height);
1018
+
1019
+ // Update label text
1020
+ NSString *labelWindowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Unknown Window";
1021
+ NSString *labelAppName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown App";
1022
+ [infoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", labelAppName, labelWindowTitle]];
1023
+
1024
+ // CRITICAL FIX: Find the select button on the TARGET screen
1025
+ NSButton *targetSelectButton = nil;
1026
+ for (NSView *subview in [targetOverlay.contentView subviews]) {
1027
+ if ([subview isKindOfClass:[NSButton class]] &&
1028
+ [[(NSButton*)subview title] isEqualToString:@"Start Record"]) {
1029
+ targetSelectButton = (NSButton*)subview;
1030
+ break;
1031
+ }
1032
+ }
1033
+
1034
+ // Create select button on target screen if not exists
1035
+ if (!targetSelectButton) {
1036
+ targetSelectButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 60)];
1037
+ [targetSelectButton setTitle:@"Start Record"];
1038
+ [targetSelectButton setButtonType:NSButtonTypeMomentaryPushIn];
1039
+ [targetSelectButton setBordered:NO];
1040
+ [targetSelectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightRegular]];
1041
+
1042
+ // Modern button styling with new RGB color
1043
+ [targetSelectButton setWantsLayer:YES];
1044
+ [targetSelectButton.layer setBackgroundColor:[[NSColor colorWithRed:90.0/255.0 green:50.0/255.0 blue:250.0/255.0 alpha:1.0] CGColor]];
1045
+ [targetSelectButton.layer setCornerRadius:8.0];
1046
+ [targetSelectButton.layer setBorderWidth:0.0];
1047
+ [targetSelectButton.layer setMasksToBounds:YES];
1048
+
1049
+ // Clean white text - normal weight
1050
+ NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
1051
+ initWithString:[targetSelectButton title]];
1052
+ [titleString addAttribute:NSForegroundColorAttributeName
1053
+ value:[NSColor whiteColor]
1054
+ range:NSMakeRange(0, [titleString length])];
1055
+ [targetSelectButton setAttributedTitle:titleString];
1056
+
1057
+ // Set target and action
1058
+ if (!g_delegate) {
1059
+ g_delegate = [[WindowSelectorDelegate alloc] init];
1060
+ }
1061
+ [targetSelectButton setTarget:g_delegate];
1062
+ [targetSelectButton setAction:@selector(selectButtonClicked:)];
1063
+
1064
+ // Remove focus ring and other default button behaviors
1065
+ [targetSelectButton setFocusRingType:NSFocusRingTypeNone];
1066
+ [targetSelectButton setShowsBorderOnlyWhileMouseInside:NO];
1067
+
1068
+ [targetOverlay.contentView addSubview:targetSelectButton];
1069
+ }
1070
+
1071
+ // Position buttons - Start Record in center of selected window
1072
+ if (targetSelectButton) {
1073
+ NSSize buttonSize = [targetSelectButton frame].size;
1074
+ // Use local window center for positioning
1075
+ CGFloat localWindowCenterX = localX + (width / 2);
1076
+ CGFloat localWindowCenterY = localY + (height / 2);
1077
+ NSPoint buttonCenter = NSMakePoint(
1078
+ localWindowCenterX - (buttonSize.width / 2),
1079
+ localWindowCenterY - (buttonSize.height / 2) // Perfect center of window
1080
+ );
1081
+
1082
+ NSLog(@" ButtonCalc: WindowCenter(%.0f, %.0f) -> Button(%.0f, %.0f)",
1083
+ localWindowCenterX, localWindowCenterY, buttonCenter.x, buttonCenter.y);
1084
+
1085
+ [targetSelectButton setFrameOrigin:buttonCenter];
1086
+
1087
+ // Position app icon above window center
1088
+ NSPoint iconCenter = NSMakePoint(
1089
+ localWindowCenterX - (96 / 2), // Center horizontally on window
1090
+ localWindowCenterY + 120 // 120px above window center
1091
+ );
1092
+ [appIconView setFrameOrigin:iconCenter];
1093
+ NSLog(@"๐ŸŽฏ Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
1094
+ iconCenter.x, iconCenter.y, (float)width, (float)height);
1095
+
1096
+ // Add fast horizontal floating animation after positioning
1097
+ [appIconView.layer removeAnimationForKey:@"floatAnimationX"];
1098
+ [appIconView.layer removeAnimationForKey:@"floatAnimationY"];
1099
+
1100
+ // Faster horizontal float animation only
1101
+ CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
1102
+ floatAnimationX.fromValue = @(-4.0);
1103
+ floatAnimationX.toValue = @(4.0);
1104
+ floatAnimationX.duration = 1.0; // Much faster animation
1105
+ floatAnimationX.repeatCount = HUGE_VALF;
1106
+ floatAnimationX.autoreverses = YES;
1107
+ floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
1108
+ [appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
1109
+
1110
+ // Position info label between icon and button relative to window center
1111
+ NSPoint labelCenter = NSMakePoint(
1112
+ localWindowCenterX - ([infoLabel frame].size.width / 2), // Center horizontally on window
1113
+ localWindowCenterY + 50 // 50px above window center, below icon
1114
+ );
1115
+ [infoLabel setFrameOrigin:labelCenter];
1116
+
1117
+ // CRITICAL FIX: Find and position cancel button on the TARGET screen
1118
+ NSButton *targetCancelButton = nil;
1119
+ for (NSView *subview in [targetOverlay.contentView subviews]) {
1120
+ if ([subview isKindOfClass:[NSButton class]] &&
1121
+ [[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
1122
+ targetCancelButton = (NSButton*)subview;
1123
+ break;
1124
+ }
1125
+ }
1126
+
1127
+ // Create cancel button on target screen if not exists
1128
+ if (!targetCancelButton) {
1129
+ targetCancelButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
1130
+ [targetCancelButton setTitle:@"Cancel"];
1131
+ [targetCancelButton setButtonType:NSButtonTypeMomentaryPushIn];
1132
+ [targetCancelButton setBordered:NO];
1133
+ [targetCancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightRegular]];
1134
+
1135
+ // Modern cancel button styling - darker gray, clean
1136
+ [targetCancelButton setWantsLayer:YES];
1137
+ [targetCancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.8] CGColor]];
1138
+ [targetCancelButton.layer setCornerRadius:6.0];
1139
+ [targetCancelButton.layer setBorderWidth:0.0];
1140
+ [targetCancelButton.layer setMasksToBounds:YES];
1141
+
1142
+ // Clean white text
1143
+ NSMutableAttributedString *cancelTitleString = [[NSMutableAttributedString alloc]
1144
+ initWithString:[targetCancelButton title]];
1145
+ [cancelTitleString addAttribute:NSForegroundColorAttributeName
1146
+ value:[NSColor whiteColor]
1147
+ range:NSMakeRange(0, [cancelTitleString length])];
1148
+ [targetCancelButton setAttributedTitle:cancelTitleString];
1149
+
1150
+ // Set target and action
1151
+ if (!g_delegate) {
1152
+ g_delegate = [[WindowSelectorDelegate alloc] init];
1153
+ }
1154
+ [targetCancelButton setTarget:g_delegate];
1155
+ [targetCancelButton setAction:@selector(cancelButtonClicked:)];
1156
+
1157
+ // Remove focus ring and other default button behaviors
1158
+ [targetCancelButton setFocusRingType:NSFocusRingTypeNone];
1159
+ [targetCancelButton setShowsBorderOnlyWhileMouseInside:NO];
1160
+
1161
+ [targetOverlay.contentView addSubview:targetCancelButton];
1162
+ }
1163
+
1164
+ if (targetCancelButton) {
1165
+ NSSize cancelButtonSize = [targetCancelButton frame].size;
1166
+ NSPoint cancelButtonCenter = NSMakePoint(
1167
+ localWindowCenterX - (cancelButtonSize.width / 2), // Center horizontally on window
1168
+ localWindowCenterY - 80 // 80px below window center
1169
+ );
1170
+ [targetCancelButton setFrameOrigin:cancelButtonCenter];
1171
+ }
1172
+ }
1119
1173
 
1120
1174
  [targetOverlay orderFront:nil];
1175
+ // DON'T make key - prevents focus ring
1176
+ // [g_overlayWindow makeKeyAndOrderFront:nil];
1177
+
1178
+ // Ensure subviews (except overlay view itself) have no borders after positioning
1179
+ for (NSView *subview in [targetOverlay.contentView subviews]) {
1180
+ // Skip the main overlay view - it handles its own borders
1181
+ if ([subview isKindOfClass:[WindowSelectorOverlayView class]]) continue;
1182
+
1183
+ if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
1184
+ [subview setWantsLayer:YES];
1185
+ if (subview.layer) {
1186
+ subview.layer.borderWidth = 1.0;
1187
+ subview.layer.borderColor = [[NSColor clearColor] CGColor];
1188
+ subview.layer.masksToBounds = YES;
1189
+ subview.layer.shadowOpacity = 0.0;
1190
+ subview.layer.shadowRadius = 0.0;
1191
+ subview.layer.shadowOffset = NSMakeSize(0, 0);
1192
+ }
1193
+ }
1194
+ }
1121
1195
 
1122
- NSLog(@" โœ… Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s",
1123
- [targetOverlay level], [targetOverlay alphaValue],
1124
- [targetOverlay isVisible] ? "YES" : "NO");
1196
+ NSLog(@" โœ… Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
1197
+ [g_overlayWindow level], [g_overlayWindow alphaValue],
1198
+ [g_overlayWindow isVisible] ? "YES" : "NO");
1125
1199
  } else if (!windowUnderCursor && g_currentWindowUnderCursor) {
1126
1200
  // No window under cursor and no toggle active, hide overlay
1127
1201
  NSString *leftWindowTitle = [g_currentWindowUnderCursor objectForKey:@"title"] ?: @"Untitled";
@@ -1900,12 +1974,6 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1900
1974
 
1901
1975
  NSLog(@"๐Ÿ–ฅ๏ธ Creating per-screen overlays for %lu displays", [allScreens count]);
1902
1976
 
1903
- // Ensure delegate exists for all overlays
1904
- if (!g_delegate) {
1905
- g_delegate = [[WindowSelectorDelegate alloc] init];
1906
- NSLog(@"๐ŸŽ›๏ธ Created global delegate for all overlays");
1907
- }
1908
-
1909
1977
  // Create overlay for each screen
1910
1978
  for (NSInteger i = 0; i < [allScreens count]; i++) {
1911
1979
  NSScreen *screen = [allScreens objectAtIndex:i];