node-mac-recorder 2.4.11 → 2.4.13
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/binding.gyp +5 -12
- package/index.js +25 -104
- package/install.js +2 -19
- package/package.json +2 -6
- package/src/audio_capture.mm +40 -96
- package/src/cursor_tracker.mm +4 -3
- package/src/mac_recorder.mm +673 -753
- package/src/screen_capture.h +0 -5
- package/src/screen_capture.mm +60 -139
- package/src/window_selector.mm +113 -399
- package/window-selector.js +34 -112
- package/ELECTRON-INTEGRATION.md +0 -710
- package/WINDOW_SELECTOR_USAGE.md +0 -447
- package/backup/binding.gyp +0 -44
- package/backup/src/audio_capture.mm +0 -116
- package/backup/src/cursor_tracker.mm +0 -518
- package/backup/src/mac_recorder.mm +0 -829
- package/backup/src/screen_capture.h +0 -19
- package/backup/src/screen_capture.mm +0 -162
- package/backup/src/screen_capture_kit.h +0 -15
- package/backup/src/window_selector.mm +0 -1457
- package/electron-window-selector.js +0 -698
- package/node-mac-recorder-2.4.2.tgz +0 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/test-api-compatibility.js +0 -92
- package/test-audio.js +0 -94
- package/test-comprehensive.js +0 -164
- package/test-electron-window-selector.js +0 -119
- package/test-overlay-fix.js +0 -72
- package/test-recording.js +0 -142
- package/test-sck-availability.js +0 -26
- package/test-sck-simple.js +0 -37
- package/test-sck.js +0 -56
- package/test-screencapture-overlay.js +0 -54
- package/test-simple-windows.js +0 -29
- package/test-sync.js +0 -52
- package/test-window-details.js +0 -34
- package/test-windows.js +0 -57
package/src/screen_capture.h
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
#import <Foundation/Foundation.h>
|
|
5
5
|
#import <CoreGraphics/CoreGraphics.h>
|
|
6
|
-
#import <napi.h>
|
|
7
6
|
|
|
8
7
|
@interface ScreenCapture : NSObject
|
|
9
8
|
|
|
@@ -17,8 +16,4 @@
|
|
|
17
16
|
|
|
18
17
|
@end
|
|
19
18
|
|
|
20
|
-
// NAPI function declarations for legacy fallback
|
|
21
|
-
Napi::Value GetAvailableDisplays(const Napi::CallbackInfo& info);
|
|
22
|
-
Napi::Value GetWindowList(const Napi::CallbackInfo& info);
|
|
23
|
-
|
|
24
19
|
#endif // SCREEN_CAPTURE_H
|
package/src/screen_capture.mm
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
#import "screen_capture.h"
|
|
2
|
-
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
3
1
|
#import <AVFoundation/AVFoundation.h>
|
|
4
2
|
#import <CoreGraphics/CoreGraphics.h>
|
|
5
3
|
#import <AppKit/AppKit.h>
|
|
6
4
|
|
|
5
|
+
@interface ScreenCapture : NSObject
|
|
6
|
+
|
|
7
|
+
+ (NSArray *)getAvailableDisplays;
|
|
8
|
+
+ (BOOL)captureDisplay:(CGDirectDisplayID)displayID
|
|
9
|
+
toFile:(NSString *)filePath
|
|
10
|
+
rect:(CGRect)rect
|
|
11
|
+
includeCursor:(BOOL)includeCursor;
|
|
12
|
+
+ (CGImageRef)createScreenshotFromDisplay:(CGDirectDisplayID)displayID
|
|
13
|
+
rect:(CGRect)rect;
|
|
14
|
+
|
|
15
|
+
@end
|
|
16
|
+
|
|
7
17
|
@implementation ScreenCapture
|
|
8
18
|
|
|
9
19
|
+ (NSArray *)getAvailableDisplays {
|
|
@@ -74,7 +84,7 @@
|
|
|
74
84
|
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
|
75
85
|
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(
|
|
76
86
|
(__bridge CFURLRef)fileURL,
|
|
77
|
-
|
|
87
|
+
kUTTypePNG,
|
|
78
88
|
1,
|
|
79
89
|
NULL
|
|
80
90
|
);
|
|
@@ -86,15 +96,51 @@
|
|
|
86
96
|
|
|
87
97
|
// Add cursor if requested
|
|
88
98
|
if (includeCursor) {
|
|
89
|
-
//
|
|
90
|
-
|
|
99
|
+
// Get cursor position
|
|
100
|
+
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
101
|
+
|
|
102
|
+
// Create mutable image context
|
|
103
|
+
size_t width = CGImageGetWidth(screenshot);
|
|
104
|
+
size_t height = CGImageGetHeight(screenshot);
|
|
105
|
+
|
|
106
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
107
|
+
CGContextRef context = CGBitmapContextCreate(
|
|
108
|
+
NULL, width, height, 8, width * 4,
|
|
109
|
+
colorSpace, kCGImageAlphaPremultipliedFirst
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (context) {
|
|
113
|
+
// Draw original screenshot
|
|
114
|
+
CGContextDrawImage(context, CGRectMake(0, 0, width, height), screenshot);
|
|
115
|
+
|
|
116
|
+
// Draw cursor (simplified - just a small circle)
|
|
117
|
+
CGRect displayBounds = CGDisplayBounds(displayID);
|
|
118
|
+
CGFloat relativeX = cursorPos.x - displayBounds.origin.x;
|
|
119
|
+
CGFloat relativeY = height - (cursorPos.y - displayBounds.origin.y);
|
|
120
|
+
|
|
121
|
+
if (!CGRectIsNull(rect)) {
|
|
122
|
+
relativeX -= rect.origin.x;
|
|
123
|
+
relativeY -= rect.origin.y;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (relativeX >= 0 && relativeX < width && relativeY >= 0 && relativeY < height) {
|
|
127
|
+
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 0.8); // Red cursor
|
|
128
|
+
CGContextFillEllipseInRect(context, CGRectMake(relativeX - 5, relativeY - 5, 10, 10));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
CGImageRef finalImage = CGBitmapContextCreateImage(context);
|
|
132
|
+
CGContextRelease(context);
|
|
133
|
+
CGImageRelease(screenshot);
|
|
134
|
+
screenshot = finalImage;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
CGColorSpaceRelease(colorSpace);
|
|
91
138
|
}
|
|
92
139
|
|
|
93
|
-
//
|
|
140
|
+
// Save image
|
|
94
141
|
CGImageDestinationAddImage(destination, screenshot, NULL);
|
|
95
142
|
BOOL success = CGImageDestinationFinalize(destination);
|
|
96
143
|
|
|
97
|
-
// Cleanup
|
|
98
144
|
CFRelease(destination);
|
|
99
145
|
CGImageRelease(screenshot);
|
|
100
146
|
|
|
@@ -103,139 +149,14 @@
|
|
|
103
149
|
|
|
104
150
|
+ (CGImageRef)createScreenshotFromDisplay:(CGDirectDisplayID)displayID
|
|
105
151
|
rect:(CGRect)rect {
|
|
106
|
-
if (CGRectIsNull(rect) || CGRectIsEmpty(rect)) {
|
|
107
|
-
rect = CGDisplayBounds(displayID);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return CGDisplayCreateImageForRect(displayID, rect);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@end
|
|
114
|
-
|
|
115
|
-
// NAPI Functions for Legacy Fallback
|
|
116
|
-
|
|
117
|
-
// NAPI Function: Get Available Displays (Legacy)
|
|
118
|
-
Napi::Value GetAvailableDisplays(const Napi::CallbackInfo& info) {
|
|
119
|
-
Napi::Env env = info.Env();
|
|
120
|
-
|
|
121
|
-
NSArray *displays = [ScreenCapture getAvailableDisplays];
|
|
122
|
-
Napi::Array displaysArray = Napi::Array::New(env);
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
displayObj.Set("width", Napi::Number::New(env, [[displayInfo objectForKey:@"width"] doubleValue]));
|
|
131
|
-
displayObj.Set("height", Napi::Number::New(env, [[displayInfo objectForKey:@"height"] doubleValue]));
|
|
132
|
-
|
|
133
|
-
// Create frame object
|
|
134
|
-
Napi::Object frameObj = Napi::Object::New(env);
|
|
135
|
-
frameObj.Set("x", Napi::Number::New(env, [[displayInfo objectForKey:@"x"] doubleValue]));
|
|
136
|
-
frameObj.Set("y", Napi::Number::New(env, [[displayInfo objectForKey:@"y"] doubleValue]));
|
|
137
|
-
frameObj.Set("width", Napi::Number::New(env, [[displayInfo objectForKey:@"width"] doubleValue]));
|
|
138
|
-
frameObj.Set("height", Napi::Number::New(env, [[displayInfo objectForKey:@"height"] doubleValue]));
|
|
139
|
-
|
|
140
|
-
displayObj.Set("frame", frameObj);
|
|
141
|
-
displayObj.Set("isPrimary", Napi::Boolean::New(env, [[displayInfo objectForKey:@"isPrimary"] boolValue]));
|
|
142
|
-
|
|
143
|
-
displaysArray.Set(static_cast<uint32_t>(i), displayObj);
|
|
153
|
+
if (CGRectIsNull(rect)) {
|
|
154
|
+
// Capture entire display
|
|
155
|
+
return CGDisplayCreateImage(displayID);
|
|
156
|
+
} else {
|
|
157
|
+
// Capture specific rect
|
|
158
|
+
return CGDisplayCreateImageForRect(displayID, rect);
|
|
144
159
|
}
|
|
145
|
-
|
|
146
|
-
return displaysArray;
|
|
147
160
|
}
|
|
148
161
|
|
|
149
|
-
|
|
150
|
-
Napi::Value GetWindowList(const Napi::CallbackInfo& info) {
|
|
151
|
-
Napi::Env env = info.Env();
|
|
152
|
-
|
|
153
|
-
// Get window list using CGWindowList
|
|
154
|
-
CFArrayRef windowList = CGWindowListCopyWindowInfo(
|
|
155
|
-
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
|
|
156
|
-
kCGNullWindowID
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
if (!windowList) {
|
|
160
|
-
return Napi::Array::New(env);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
Napi::Array windowsArray = Napi::Array::New(env);
|
|
164
|
-
CFIndex count = CFArrayGetCount(windowList);
|
|
165
|
-
uint32_t index = 0;
|
|
166
|
-
|
|
167
|
-
for (CFIndex i = 0; i < count; i++) {
|
|
168
|
-
CFDictionaryRef windowInfo = (CFDictionaryRef)CFArrayGetValueAtIndex(windowList, i);
|
|
169
|
-
|
|
170
|
-
// Get window ID
|
|
171
|
-
CFNumberRef windowIDRef = (CFNumberRef)CFDictionaryGetValue(windowInfo, kCGWindowNumber);
|
|
172
|
-
uint32_t windowID;
|
|
173
|
-
CFNumberGetValue(windowIDRef, kCFNumberSInt32Type, &windowID);
|
|
174
|
-
|
|
175
|
-
// Get window title
|
|
176
|
-
CFStringRef windowTitleRef = (CFStringRef)CFDictionaryGetValue(windowInfo, kCGWindowName);
|
|
177
|
-
std::string windowTitle = "";
|
|
178
|
-
if (windowTitleRef) {
|
|
179
|
-
const char *titleCStr = CFStringGetCStringPtr(windowTitleRef, kCFStringEncodingUTF8);
|
|
180
|
-
if (titleCStr) {
|
|
181
|
-
windowTitle = std::string(titleCStr);
|
|
182
|
-
} else {
|
|
183
|
-
// Fallback for when CFStringGetCStringPtr returns NULL
|
|
184
|
-
CFIndex length = CFStringGetLength(windowTitleRef);
|
|
185
|
-
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
|
186
|
-
char *buffer = (char *)malloc(maxSize);
|
|
187
|
-
if (CFStringGetCString(windowTitleRef, buffer, maxSize, kCFStringEncodingUTF8)) {
|
|
188
|
-
windowTitle = std::string(buffer);
|
|
189
|
-
}
|
|
190
|
-
free(buffer);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Get owner name
|
|
195
|
-
CFStringRef ownerNameRef = (CFStringRef)CFDictionaryGetValue(windowInfo, kCGWindowOwnerName);
|
|
196
|
-
std::string ownerName = "";
|
|
197
|
-
if (ownerNameRef) {
|
|
198
|
-
const char *ownerCStr = CFStringGetCStringPtr(ownerNameRef, kCFStringEncodingUTF8);
|
|
199
|
-
if (ownerCStr) {
|
|
200
|
-
ownerName = std::string(ownerCStr);
|
|
201
|
-
} else {
|
|
202
|
-
CFIndex length = CFStringGetLength(ownerNameRef);
|
|
203
|
-
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
|
204
|
-
char *buffer = (char *)malloc(maxSize);
|
|
205
|
-
if (CFStringGetCString(ownerNameRef, buffer, maxSize, kCFStringEncodingUTF8)) {
|
|
206
|
-
ownerName = std::string(buffer);
|
|
207
|
-
}
|
|
208
|
-
free(buffer);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Get window bounds
|
|
213
|
-
CFDictionaryRef boundsRef = (CFDictionaryRef)CFDictionaryGetValue(windowInfo, kCGWindowBounds);
|
|
214
|
-
CGRect bounds = CGRectNull;
|
|
215
|
-
if (boundsRef) {
|
|
216
|
-
CGRectMakeWithDictionaryRepresentation(boundsRef, &bounds);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Filter out small/invalid windows
|
|
220
|
-
if (bounds.size.width > 50 && bounds.size.height > 50 && !windowTitle.empty()) {
|
|
221
|
-
Napi::Object windowObj = Napi::Object::New(env);
|
|
222
|
-
windowObj.Set("id", Napi::Number::New(env, windowID));
|
|
223
|
-
windowObj.Set("title", Napi::String::New(env, windowTitle));
|
|
224
|
-
windowObj.Set("ownerName", Napi::String::New(env, ownerName));
|
|
225
|
-
|
|
226
|
-
// Create bounds object
|
|
227
|
-
Napi::Object boundsObj = Napi::Object::New(env);
|
|
228
|
-
boundsObj.Set("x", Napi::Number::New(env, bounds.origin.x));
|
|
229
|
-
boundsObj.Set("y", Napi::Number::New(env, bounds.origin.y));
|
|
230
|
-
boundsObj.Set("width", Napi::Number::New(env, bounds.size.width));
|
|
231
|
-
boundsObj.Set("height", Napi::Number::New(env, bounds.size.height));
|
|
232
|
-
|
|
233
|
-
windowObj.Set("bounds", boundsObj);
|
|
234
|
-
|
|
235
|
-
windowsArray.Set(index++, windowObj);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
CFRelease(windowList);
|
|
240
|
-
return windowsArray;
|
|
241
|
-
}
|
|
162
|
+
@end
|