node-mac-recorder 2.16.19 โ 2.16.21
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 +274 -203
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''
|
|
4
|
+
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐ฏ FINAL TEST: Multi-screen UI visibility fix...'');\nconst WindowSelector = require(''./window-selector.js'');\nconst selector = new WindowSelector();\n\nselector.checkPermissions().then(perms => {\n console.log(''โ
Permissions OK'');\n return selector.startSelection();\n}).then(() => {\n console.log(''โ
Window selection started'');\n console.log('''');\n console.log(''๐ฏ CRITICAL TEST - Both screens should now have same window level:'');\n console.log(''1. Move cursor over MacBook screen window โ should see buttons + logo + title'');\n console.log(''2. Move cursor over external display window โ should ALSO see buttons + logo + title''); \n console.log(''3. Check console for \"\"Level=\"\" logs - both should be 2147483631'');\n console.log('''');\n console.log(''โก If this works, the multi-screen UI positioning bug is FIXED!'');\n \n setTimeout(() => {\n console.log(''โฐ Test complete - check both screens for UI visibility'');\n selector.stopSelection();\n }, 25000);\n}).catch(console.error);\")"
|
|
5
5
|
],
|
|
6
6
|
"deny": [],
|
|
7
7
|
"ask": []
|
package/package.json
CHANGED
package/src/window_selector.mm
CHANGED
|
@@ -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
|
-
//
|
|
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,279 @@ 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
|
-
[
|
|
929
|
+
[(WindowSelectorOverlayView *)g_overlayView setIsToggled:NO];
|
|
1113
930
|
} else {
|
|
1114
931
|
// Keep toggle state for locked window
|
|
1115
|
-
[
|
|
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
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
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
|
+
|
|
1027
|
+
for (NSView *subview in [targetOverlay.contentView subviews]) {
|
|
1028
|
+
if ([subview isKindOfClass:[NSButton class]]) {
|
|
1029
|
+
NSButton *btn = (NSButton*)subview;
|
|
1030
|
+
if ([[btn title] isEqualToString:@"Start Record"]) {
|
|
1031
|
+
targetSelectButton = btn;
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1116
1035
|
}
|
|
1117
1036
|
|
|
1118
|
-
|
|
1037
|
+
// Create select button on target screen if not exists
|
|
1038
|
+
if (!targetSelectButton) {
|
|
1039
|
+
targetSelectButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 60)];
|
|
1040
|
+
[targetSelectButton setTitle:@"Start Record"];
|
|
1041
|
+
[targetSelectButton setButtonType:NSButtonTypeMomentaryPushIn];
|
|
1042
|
+
[targetSelectButton setBordered:NO];
|
|
1043
|
+
[targetSelectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightRegular]];
|
|
1044
|
+
|
|
1045
|
+
// Modern button styling with new RGB color
|
|
1046
|
+
[targetSelectButton setWantsLayer:YES];
|
|
1047
|
+
[targetSelectButton.layer setBackgroundColor:[[NSColor colorWithRed:90.0/255.0 green:50.0/255.0 blue:250.0/255.0 alpha:1.0] CGColor]];
|
|
1048
|
+
[targetSelectButton.layer setCornerRadius:8.0];
|
|
1049
|
+
[targetSelectButton.layer setBorderWidth:0.0];
|
|
1050
|
+
[targetSelectButton.layer setMasksToBounds:YES];
|
|
1051
|
+
|
|
1052
|
+
// Clean white text - normal weight
|
|
1053
|
+
NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
|
|
1054
|
+
initWithString:[targetSelectButton title]];
|
|
1055
|
+
[titleString addAttribute:NSForegroundColorAttributeName
|
|
1056
|
+
value:[NSColor whiteColor]
|
|
1057
|
+
range:NSMakeRange(0, [titleString length])];
|
|
1058
|
+
[targetSelectButton setAttributedTitle:titleString];
|
|
1059
|
+
|
|
1060
|
+
// Set target and action
|
|
1061
|
+
if (!g_delegate) {
|
|
1062
|
+
g_delegate = [[WindowSelectorDelegate alloc] init];
|
|
1063
|
+
}
|
|
1064
|
+
[targetSelectButton setTarget:g_delegate];
|
|
1065
|
+
[targetSelectButton setAction:@selector(selectButtonClicked:)];
|
|
1066
|
+
|
|
1067
|
+
// Remove focus ring and other default button behaviors
|
|
1068
|
+
[targetSelectButton setFocusRingType:NSFocusRingTypeNone];
|
|
1069
|
+
[targetSelectButton setShowsBorderOnlyWhileMouseInside:NO];
|
|
1070
|
+
|
|
1071
|
+
[targetOverlay.contentView addSubview:targetSelectButton];
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Position buttons - Start Record in center of selected window
|
|
1075
|
+
if (targetSelectButton) {
|
|
1076
|
+
NSSize buttonSize = [targetSelectButton frame].size;
|
|
1077
|
+
// Use local window center for positioning
|
|
1078
|
+
CGFloat localWindowCenterX = localX + (width / 2);
|
|
1079
|
+
CGFloat localWindowCenterY = localY + (height / 2);
|
|
1080
|
+
NSPoint buttonCenter = NSMakePoint(
|
|
1081
|
+
localWindowCenterX - (buttonSize.width / 2),
|
|
1082
|
+
localWindowCenterY - (buttonSize.height / 2) // Perfect center of window
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
NSLog(@" ButtonCalc: WindowCenter(%.0f, %.0f) -> Button(%.0f, %.0f)",
|
|
1086
|
+
localWindowCenterX, localWindowCenterY, buttonCenter.x, buttonCenter.y);
|
|
1087
|
+
|
|
1088
|
+
[targetSelectButton setFrameOrigin:buttonCenter];
|
|
1089
|
+
|
|
1090
|
+
// Position app icon above window center
|
|
1091
|
+
NSPoint iconCenter = NSMakePoint(
|
|
1092
|
+
localWindowCenterX - (96 / 2), // Center horizontally on window
|
|
1093
|
+
localWindowCenterY + 120 // 120px above window center
|
|
1094
|
+
);
|
|
1095
|
+
[appIconView setFrameOrigin:iconCenter];
|
|
1096
|
+
NSLog(@"๐ฏ Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
|
|
1097
|
+
iconCenter.x, iconCenter.y, (float)width, (float)height);
|
|
1098
|
+
|
|
1099
|
+
// Add fast horizontal floating animation after positioning
|
|
1100
|
+
[appIconView.layer removeAnimationForKey:@"floatAnimationX"];
|
|
1101
|
+
[appIconView.layer removeAnimationForKey:@"floatAnimationY"];
|
|
1102
|
+
|
|
1103
|
+
// Faster horizontal float animation only
|
|
1104
|
+
CABasicAnimation *floatAnimationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
|
|
1105
|
+
floatAnimationX.fromValue = @(-4.0);
|
|
1106
|
+
floatAnimationX.toValue = @(4.0);
|
|
1107
|
+
floatAnimationX.duration = 1.0; // Much faster animation
|
|
1108
|
+
floatAnimationX.repeatCount = HUGE_VALF;
|
|
1109
|
+
floatAnimationX.autoreverses = YES;
|
|
1110
|
+
floatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
1111
|
+
[appIconView.layer addAnimation:floatAnimationX forKey:@"floatAnimationX"];
|
|
1112
|
+
|
|
1113
|
+
// Position info label between icon and button relative to window center
|
|
1114
|
+
NSPoint labelCenter = NSMakePoint(
|
|
1115
|
+
localWindowCenterX - ([infoLabel frame].size.width / 2), // Center horizontally on window
|
|
1116
|
+
localWindowCenterY + 50 // 50px above window center, below icon
|
|
1117
|
+
);
|
|
1118
|
+
[infoLabel setFrameOrigin:labelCenter];
|
|
1119
|
+
|
|
1120
|
+
// CRITICAL FIX: Find and position cancel button on the TARGET screen
|
|
1121
|
+
NSButton *targetCancelButton = nil;
|
|
1122
|
+
for (NSView *subview in [targetOverlay.contentView subviews]) {
|
|
1123
|
+
if ([subview isKindOfClass:[NSButton class]] &&
|
|
1124
|
+
[[(NSButton*)subview title] isEqualToString:@"Cancel"]) {
|
|
1125
|
+
targetCancelButton = (NSButton*)subview;
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Create cancel button on target screen if not exists
|
|
1131
|
+
if (!targetCancelButton) {
|
|
1132
|
+
targetCancelButton = [[HoverButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
|
|
1133
|
+
[targetCancelButton setTitle:@"Cancel"];
|
|
1134
|
+
[targetCancelButton setButtonType:NSButtonTypeMomentaryPushIn];
|
|
1135
|
+
[targetCancelButton setBordered:NO];
|
|
1136
|
+
[targetCancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightRegular]];
|
|
1137
|
+
|
|
1138
|
+
// Modern cancel button styling - darker gray, clean
|
|
1139
|
+
[targetCancelButton setWantsLayer:YES];
|
|
1140
|
+
[targetCancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.8] CGColor]];
|
|
1141
|
+
[targetCancelButton.layer setCornerRadius:6.0];
|
|
1142
|
+
[targetCancelButton.layer setBorderWidth:0.0];
|
|
1143
|
+
[targetCancelButton.layer setMasksToBounds:YES];
|
|
1144
|
+
|
|
1145
|
+
// Clean white text
|
|
1146
|
+
NSMutableAttributedString *cancelTitleString = [[NSMutableAttributedString alloc]
|
|
1147
|
+
initWithString:[targetCancelButton title]];
|
|
1148
|
+
[cancelTitleString addAttribute:NSForegroundColorAttributeName
|
|
1149
|
+
value:[NSColor whiteColor]
|
|
1150
|
+
range:NSMakeRange(0, [cancelTitleString length])];
|
|
1151
|
+
[targetCancelButton setAttributedTitle:cancelTitleString];
|
|
1152
|
+
|
|
1153
|
+
// Set target and action
|
|
1154
|
+
if (!g_delegate) {
|
|
1155
|
+
g_delegate = [[WindowSelectorDelegate alloc] init];
|
|
1156
|
+
}
|
|
1157
|
+
[targetCancelButton setTarget:g_delegate];
|
|
1158
|
+
[targetCancelButton setAction:@selector(cancelButtonClicked:)];
|
|
1159
|
+
|
|
1160
|
+
// Remove focus ring and other default button behaviors
|
|
1161
|
+
[targetCancelButton setFocusRingType:NSFocusRingTypeNone];
|
|
1162
|
+
[targetCancelButton setShowsBorderOnlyWhileMouseInside:NO];
|
|
1163
|
+
|
|
1164
|
+
[targetOverlay.contentView addSubview:targetCancelButton];
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (targetCancelButton) {
|
|
1168
|
+
NSSize cancelButtonSize = [targetCancelButton frame].size;
|
|
1169
|
+
NSPoint cancelButtonCenter = NSMakePoint(
|
|
1170
|
+
localWindowCenterX - (cancelButtonSize.width / 2), // Center horizontally on window
|
|
1171
|
+
localWindowCenterY - 80 // 80px below window center
|
|
1172
|
+
);
|
|
1173
|
+
[targetCancelButton setFrameOrigin:cancelButtonCenter];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1119
1176
|
|
|
1120
1177
|
[targetOverlay orderFront:nil];
|
|
1178
|
+
// DON'T make key - prevents focus ring
|
|
1179
|
+
// [g_overlayWindow makeKeyAndOrderFront:nil];
|
|
1180
|
+
|
|
1181
|
+
// Ensure subviews (except overlay view itself) have no borders after positioning
|
|
1182
|
+
for (NSView *subview in [targetOverlay.contentView subviews]) {
|
|
1183
|
+
// Skip the main overlay view - it handles its own borders
|
|
1184
|
+
if ([subview isKindOfClass:[WindowSelectorOverlayView class]]) continue;
|
|
1185
|
+
|
|
1186
|
+
if ([subview respondsToSelector:@selector(setWantsLayer:)]) {
|
|
1187
|
+
[subview setWantsLayer:YES];
|
|
1188
|
+
if (subview.layer) {
|
|
1189
|
+
subview.layer.borderWidth = 1.0;
|
|
1190
|
+
subview.layer.borderColor = [[NSColor clearColor] CGColor];
|
|
1191
|
+
subview.layer.masksToBounds = YES;
|
|
1192
|
+
subview.layer.shadowOpacity = 0.0;
|
|
1193
|
+
subview.layer.shadowRadius = 0.0;
|
|
1194
|
+
subview.layer.shadowOffset = NSMakeSize(0, 0);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1121
1198
|
|
|
1122
|
-
NSLog(@" โ
Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s",
|
|
1123
|
-
[
|
|
1124
|
-
[
|
|
1199
|
+
NSLog(@" โ
Overlay Status: Level=%ld, Alpha=%.1f, Visible=%s, Frame Set=YES",
|
|
1200
|
+
[g_overlayWindow level], [g_overlayWindow alphaValue],
|
|
1201
|
+
[g_overlayWindow isVisible] ? "YES" : "NO");
|
|
1125
1202
|
} else if (!windowUnderCursor && g_currentWindowUnderCursor) {
|
|
1126
1203
|
// No window under cursor and no toggle active, hide overlay
|
|
1127
1204
|
NSString *leftWindowTitle = [g_currentWindowUnderCursor objectForKey:@"title"] ?: @"Untitled";
|
|
@@ -1900,12 +1977,6 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1900
1977
|
|
|
1901
1978
|
NSLog(@"๐ฅ๏ธ Creating per-screen overlays for %lu displays", [allScreens count]);
|
|
1902
1979
|
|
|
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
1980
|
// Create overlay for each screen
|
|
1910
1981
|
for (NSInteger i = 0; i < [allScreens count]; i++) {
|
|
1911
1982
|
NSScreen *screen = [allScreens objectAtIndex:i];
|
|
@@ -1928,7 +1999,7 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
|
|
|
1928
1999
|
[screenOverlay setOpaque:NO];
|
|
1929
2000
|
[screenOverlay setIgnoresMouseEvents:NO];
|
|
1930
2001
|
[screenOverlay setAcceptsMouseMovedEvents:YES];
|
|
1931
|
-
[screenOverlay setLevel:
|
|
2002
|
+
[screenOverlay setLevel:CGWindowLevelForKey(kCGMaximumWindowLevelKey)]; // CRITICAL FIX: Match highest level
|
|
1932
2003
|
[screenOverlay setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary];
|
|
1933
2004
|
|
|
1934
2005
|
NSLog(@" Screen %ld: (%.0f,%.0f) %.0fx%.0f โ Overlay created", i, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
|