node-mac-recorder 1.2.3 → 1.2.4
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/capture-test.js +87 -0
- package/index.js +66 -26
- package/list-test.js +46 -0
- package/package.json +1 -1
- package/src/mac_recorder.mm +87 -52
- package/src/screen_capture.h +19 -0
package/capture-test.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const MacRecorder = require("./");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
async function saveBase64Image(base64String, filePath) {
|
|
6
|
+
// Remove the data:image/png;base64, prefix if it exists
|
|
7
|
+
const base64Data = base64String.replace(/^data:image\/png;base64,/, "");
|
|
8
|
+
|
|
9
|
+
// Create directory if it doesn't exist
|
|
10
|
+
const dir = path.dirname(filePath);
|
|
11
|
+
if (!fs.existsSync(dir)) {
|
|
12
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Write the file
|
|
16
|
+
fs.writeFileSync(filePath, base64Data, "base64");
|
|
17
|
+
console.log(`✅ Saved image to: ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function captureTest() {
|
|
21
|
+
const recorder = new MacRecorder();
|
|
22
|
+
|
|
23
|
+
// Create output directory
|
|
24
|
+
const outputDir = path.join(__dirname, "thumbnails");
|
|
25
|
+
if (!fs.existsSync(outputDir)) {
|
|
26
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log("📸 Testing Display Capture");
|
|
30
|
+
|
|
31
|
+
// Get displays
|
|
32
|
+
const displays = await recorder.getDisplays();
|
|
33
|
+
console.log(`Found ${displays.length} displays`);
|
|
34
|
+
|
|
35
|
+
// Capture each display
|
|
36
|
+
for (const display of displays) {
|
|
37
|
+
console.log(
|
|
38
|
+
`\nCapturing display ${display.id} (${display.width}x${display.height})`
|
|
39
|
+
);
|
|
40
|
+
try {
|
|
41
|
+
const thumbnail = await recorder.getDisplayThumbnail(display.id, {
|
|
42
|
+
maxWidth: 800,
|
|
43
|
+
maxHeight: 600,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const fileName = `display_${display.id}.png`;
|
|
47
|
+
const filePath = path.join(outputDir, fileName);
|
|
48
|
+
await saveBase64Image(thumbnail, filePath);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Failed to capture display ${display.id}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("\n📸 Testing Window Capture");
|
|
55
|
+
|
|
56
|
+
// Get windows
|
|
57
|
+
const windows = await recorder.getWindows();
|
|
58
|
+
console.log(`Found ${windows.length} windows`);
|
|
59
|
+
|
|
60
|
+
// Capture each window
|
|
61
|
+
for (const window of windows) {
|
|
62
|
+
console.log(
|
|
63
|
+
`\nCapturing window "${window.appName}" (${window.width}x${window.height})`
|
|
64
|
+
);
|
|
65
|
+
try {
|
|
66
|
+
const thumbnail = await recorder.getWindowThumbnail(window.id, {
|
|
67
|
+
maxWidth: 800,
|
|
68
|
+
maxHeight: 600,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const fileName = `window_${window.id}_${window.appName.replace(
|
|
72
|
+
/[^a-z0-9]/gi,
|
|
73
|
+
"_"
|
|
74
|
+
)}.png`;
|
|
75
|
+
const filePath = path.join(outputDir, fileName);
|
|
76
|
+
await saveBase64Image(thumbnail, filePath);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`Failed to capture window "${window.appName}":`, error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(
|
|
83
|
+
"\n✅ Test completed. Check the thumbnails directory for results."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
captureTest().catch(console.error);
|
package/index.js
CHANGED
|
@@ -69,31 +69,17 @@ class MacRecorder extends EventEmitter {
|
|
|
69
69
|
* macOS ekranlarını listeler
|
|
70
70
|
*/
|
|
71
71
|
async getDisplays() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
? `${display.width}x${display.height}`
|
|
84
|
-
: "Unknown",
|
|
85
|
-
width: typeof display === "object" ? display.width : null,
|
|
86
|
-
height: typeof display === "object" ? display.height : null,
|
|
87
|
-
x: typeof display === "object" ? display.x : 0,
|
|
88
|
-
y: typeof display === "object" ? display.y : 0,
|
|
89
|
-
isPrimary:
|
|
90
|
-
typeof display === "object" ? display.isPrimary : index === 0,
|
|
91
|
-
}));
|
|
92
|
-
resolve(formattedDisplays);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
reject(error);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
72
|
+
const displays = nativeBinding.getDisplays();
|
|
73
|
+
return displays.map((display, index) => ({
|
|
74
|
+
id: display.id, // Use the actual display ID from native code
|
|
75
|
+
name: display.name,
|
|
76
|
+
width: display.width,
|
|
77
|
+
height: display.height,
|
|
78
|
+
x: display.x,
|
|
79
|
+
y: display.y,
|
|
80
|
+
isPrimary: display.isPrimary,
|
|
81
|
+
resolution: `${display.width}x${display.height}`,
|
|
82
|
+
}));
|
|
97
83
|
}
|
|
98
84
|
|
|
99
85
|
/**
|
|
@@ -404,8 +390,16 @@ class MacRecorder extends EventEmitter {
|
|
|
404
390
|
|
|
405
391
|
return new Promise((resolve, reject) => {
|
|
406
392
|
try {
|
|
393
|
+
// Get all displays first to validate the ID
|
|
394
|
+
const displays = nativeBinding.getDisplays();
|
|
395
|
+
const display = displays.find((d) => d.id === displayId);
|
|
396
|
+
|
|
397
|
+
if (!display) {
|
|
398
|
+
throw new Error(`Display with ID ${displayId} not found`);
|
|
399
|
+
}
|
|
400
|
+
|
|
407
401
|
const base64Image = nativeBinding.getDisplayThumbnail(
|
|
408
|
-
|
|
402
|
+
display.id, // Use the actual CGDirectDisplayID
|
|
409
403
|
maxWidth,
|
|
410
404
|
maxHeight
|
|
411
405
|
);
|
|
@@ -586,6 +580,52 @@ class MacRecorder extends EventEmitter {
|
|
|
586
580
|
nativeModule: "mac_recorder.node",
|
|
587
581
|
};
|
|
588
582
|
}
|
|
583
|
+
|
|
584
|
+
async getDisplaysWithThumbnails(options = {}) {
|
|
585
|
+
const displays = await this.getDisplays();
|
|
586
|
+
|
|
587
|
+
// Get thumbnails for each display
|
|
588
|
+
const displayPromises = displays.map(async (display) => {
|
|
589
|
+
try {
|
|
590
|
+
const thumbnail = await this.getDisplayThumbnail(display.id, options);
|
|
591
|
+
return {
|
|
592
|
+
...display,
|
|
593
|
+
thumbnail,
|
|
594
|
+
};
|
|
595
|
+
} catch (error) {
|
|
596
|
+
return {
|
|
597
|
+
...display,
|
|
598
|
+
thumbnail: null,
|
|
599
|
+
thumbnailError: error.message,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return Promise.all(displayPromises);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async getWindowsWithThumbnails(options = {}) {
|
|
608
|
+
const windows = await this.getWindows();
|
|
609
|
+
|
|
610
|
+
// Get thumbnails for each window
|
|
611
|
+
const windowPromises = windows.map(async (window) => {
|
|
612
|
+
try {
|
|
613
|
+
const thumbnail = await this.getWindowThumbnail(window.id, options);
|
|
614
|
+
return {
|
|
615
|
+
...window,
|
|
616
|
+
thumbnail,
|
|
617
|
+
};
|
|
618
|
+
} catch (error) {
|
|
619
|
+
return {
|
|
620
|
+
...window,
|
|
621
|
+
thumbnail: null,
|
|
622
|
+
thumbnailError: error.message,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
return Promise.all(windowPromises);
|
|
628
|
+
}
|
|
589
629
|
}
|
|
590
630
|
|
|
591
631
|
module.exports = MacRecorder;
|
package/list-test.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const MacRecorder = require("./");
|
|
2
|
+
|
|
3
|
+
async function testListing() {
|
|
4
|
+
const recorder = new MacRecorder();
|
|
5
|
+
|
|
6
|
+
console.log("📺 Testing Displays with Thumbnails");
|
|
7
|
+
const displays = await recorder.getDisplaysWithThumbnails({
|
|
8
|
+
maxWidth: 200,
|
|
9
|
+
maxHeight: 150,
|
|
10
|
+
});
|
|
11
|
+
console.log(`Found ${displays.length} displays:`);
|
|
12
|
+
for (const display of displays) {
|
|
13
|
+
console.log(`\nDisplay ID: ${display.id}`);
|
|
14
|
+
console.log(`Resolution: ${display.width}x${display.height}`);
|
|
15
|
+
console.log(`Position: (${display.x}, ${display.y})`);
|
|
16
|
+
console.log(`Primary: ${display.isPrimary}`);
|
|
17
|
+
console.log(
|
|
18
|
+
`Thumbnail included: ${display.thumbnail.substring(0, 50)}... (${
|
|
19
|
+
display.thumbnail.length
|
|
20
|
+
} chars)`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("\n🪟 Testing Windows with Thumbnails");
|
|
25
|
+
const windows = await recorder.getWindowsWithThumbnails({
|
|
26
|
+
maxWidth: 200,
|
|
27
|
+
maxHeight: 150,
|
|
28
|
+
});
|
|
29
|
+
console.log(`Found ${windows.length} windows:`);
|
|
30
|
+
for (const window of windows.slice(0, 3)) {
|
|
31
|
+
// Just show first 3 for brevity
|
|
32
|
+
console.log(`\nWindow: ${window.appName}`);
|
|
33
|
+
console.log(`ID: ${window.id}`);
|
|
34
|
+
console.log(`Size: ${window.width}x${window.height}`);
|
|
35
|
+
console.log(
|
|
36
|
+
`Thumbnail included: ${window.thumbnail.substring(0, 50)}... (${
|
|
37
|
+
window.thumbnail.length
|
|
38
|
+
} chars)`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (windows.length > 3) {
|
|
42
|
+
console.log(`\n... and ${windows.length - 3} more windows`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
testListing().catch(console.error);
|
package/package.json
CHANGED
package/src/mac_recorder.mm
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
#import <ImageIO/ImageIO.h>
|
|
8
8
|
#import <CoreAudio/CoreAudio.h>
|
|
9
9
|
|
|
10
|
+
// Import screen capture
|
|
11
|
+
#import "screen_capture.h"
|
|
12
|
+
|
|
10
13
|
// Cursor tracker function declarations
|
|
11
14
|
Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports);
|
|
12
15
|
|
|
@@ -363,35 +366,20 @@ Napi::Value GetDisplays(const Napi::CallbackInfo& info) {
|
|
|
363
366
|
Napi::Env env = info.Env();
|
|
364
367
|
|
|
365
368
|
@try {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
uint32_t displayCount;
|
|
369
|
-
CGGetActiveDisplayList(0, NULL, &displayCount);
|
|
370
|
-
|
|
371
|
-
CGDirectDisplayID *displayList = (CGDirectDisplayID *)malloc(displayCount * sizeof(CGDirectDisplayID));
|
|
372
|
-
CGGetActiveDisplayList(displayCount, displayList, &displayCount);
|
|
373
|
-
|
|
374
|
-
for (uint32_t i = 0; i < displayCount; i++) {
|
|
375
|
-
CGDirectDisplayID displayID = displayList[i];
|
|
376
|
-
CGRect bounds = CGDisplayBounds(displayID);
|
|
377
|
-
|
|
378
|
-
[displays addObject:@{
|
|
379
|
-
@"id": @(displayID),
|
|
380
|
-
@"name": [NSString stringWithFormat:@"Display %d", i + 1],
|
|
381
|
-
@"width": @(bounds.size.width),
|
|
382
|
-
@"height": @(bounds.size.height),
|
|
383
|
-
@"x": @(bounds.origin.x),
|
|
384
|
-
@"y": @(bounds.origin.y),
|
|
385
|
-
@"isPrimary": @(CGDisplayIsMain(displayID))
|
|
386
|
-
}];
|
|
387
|
-
}
|
|
369
|
+
NSArray *displays = [ScreenCapture getAvailableDisplays];
|
|
370
|
+
Napi::Array result = Napi::Array::New(env, displays.count);
|
|
388
371
|
|
|
389
|
-
|
|
372
|
+
NSLog(@"Found %lu displays", (unsigned long)displays.count);
|
|
390
373
|
|
|
391
|
-
// Convert to NAPI array
|
|
392
|
-
Napi::Array result = Napi::Array::New(env, displays.count);
|
|
393
374
|
for (NSUInteger i = 0; i < displays.count; i++) {
|
|
394
375
|
NSDictionary *display = displays[i];
|
|
376
|
+
NSLog(@"Display %lu: ID=%u, Name=%@, Size=%@x%@",
|
|
377
|
+
(unsigned long)i,
|
|
378
|
+
[display[@"id"] unsignedIntValue],
|
|
379
|
+
display[@"name"],
|
|
380
|
+
display[@"width"],
|
|
381
|
+
display[@"height"]);
|
|
382
|
+
|
|
395
383
|
Napi::Object displayObj = Napi::Object::New(env);
|
|
396
384
|
displayObj.Set("id", Napi::Number::New(env, [display[@"id"] unsignedIntValue]));
|
|
397
385
|
displayObj.Set("name", Napi::String::New(env, [display[@"name"] UTF8String]));
|
|
@@ -406,6 +394,7 @@ Napi::Value GetDisplays(const Napi::CallbackInfo& info) {
|
|
|
406
394
|
return result;
|
|
407
395
|
|
|
408
396
|
} @catch (NSException *exception) {
|
|
397
|
+
NSLog(@"Exception in GetDisplays: %@", exception);
|
|
409
398
|
return Napi::Array::New(env, 0);
|
|
410
399
|
}
|
|
411
400
|
}
|
|
@@ -536,10 +525,34 @@ Napi::Value GetDisplayThumbnail(const Napi::CallbackInfo& info) {
|
|
|
536
525
|
}
|
|
537
526
|
|
|
538
527
|
@try {
|
|
528
|
+
// Verify display exists
|
|
529
|
+
CGDirectDisplayID activeDisplays[32];
|
|
530
|
+
uint32_t displayCount;
|
|
531
|
+
CGError err = CGGetActiveDisplayList(32, activeDisplays, &displayCount);
|
|
532
|
+
|
|
533
|
+
if (err != kCGErrorSuccess) {
|
|
534
|
+
NSLog(@"Failed to get active display list: %d", err);
|
|
535
|
+
return env.Null();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
bool displayFound = false;
|
|
539
|
+
for (uint32_t i = 0; i < displayCount; i++) {
|
|
540
|
+
if (activeDisplays[i] == displayID) {
|
|
541
|
+
displayFound = true;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!displayFound) {
|
|
547
|
+
NSLog(@"Display ID %u not found in active displays", displayID);
|
|
548
|
+
return env.Null();
|
|
549
|
+
}
|
|
550
|
+
|
|
539
551
|
// Create display image
|
|
540
552
|
CGImageRef displayImage = CGDisplayCreateImage(displayID);
|
|
541
553
|
|
|
542
554
|
if (!displayImage) {
|
|
555
|
+
NSLog(@"CGDisplayCreateImage failed for display ID: %u", displayID);
|
|
543
556
|
return env.Null();
|
|
544
557
|
}
|
|
545
558
|
|
|
@@ -547,6 +560,8 @@ Napi::Value GetDisplayThumbnail(const Napi::CallbackInfo& info) {
|
|
|
547
560
|
size_t originalWidth = CGImageGetWidth(displayImage);
|
|
548
561
|
size_t originalHeight = CGImageGetHeight(displayImage);
|
|
549
562
|
|
|
563
|
+
NSLog(@"Original dimensions: %zux%zu", originalWidth, originalHeight);
|
|
564
|
+
|
|
550
565
|
// Calculate scaled dimensions maintaining aspect ratio
|
|
551
566
|
double scaleX = (double)maxWidth / originalWidth;
|
|
552
567
|
double scaleY = (double)maxHeight / originalHeight;
|
|
@@ -555,6 +570,8 @@ Napi::Value GetDisplayThumbnail(const Napi::CallbackInfo& info) {
|
|
|
555
570
|
size_t thumbnailWidth = (size_t)(originalWidth * scale);
|
|
556
571
|
size_t thumbnailHeight = (size_t)(originalHeight * scale);
|
|
557
572
|
|
|
573
|
+
NSLog(@"Thumbnail dimensions: %zux%zu (scale: %f)", thumbnailWidth, thumbnailHeight, scale);
|
|
574
|
+
|
|
558
575
|
// Create scaled image
|
|
559
576
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
560
577
|
CGContextRef context = CGBitmapContextCreate(
|
|
@@ -564,43 +581,61 @@ Napi::Value GetDisplayThumbnail(const Napi::CallbackInfo& info) {
|
|
|
564
581
|
8,
|
|
565
582
|
thumbnailWidth * 4,
|
|
566
583
|
colorSpace,
|
|
567
|
-
kCGImageAlphaPremultipliedLast
|
|
584
|
+
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
|
|
568
585
|
);
|
|
569
586
|
|
|
570
|
-
if (context) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
587
|
+
if (!context) {
|
|
588
|
+
NSLog(@"Failed to create bitmap context");
|
|
589
|
+
CGImageRelease(displayImage);
|
|
590
|
+
CGColorSpaceRelease(colorSpace);
|
|
591
|
+
return env.Null();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Set interpolation quality for better scaling
|
|
595
|
+
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
|
|
596
|
+
|
|
597
|
+
// Draw the image
|
|
598
|
+
CGContextDrawImage(context, CGRectMake(0, 0, thumbnailWidth, thumbnailHeight), displayImage);
|
|
599
|
+
CGImageRef thumbnailImage = CGBitmapContextCreateImage(context);
|
|
600
|
+
|
|
601
|
+
if (!thumbnailImage) {
|
|
602
|
+
NSLog(@"Failed to create thumbnail image");
|
|
603
|
+
CGContextRelease(context);
|
|
604
|
+
CGImageRelease(displayImage);
|
|
605
|
+
CGColorSpaceRelease(colorSpace);
|
|
606
|
+
return env.Null();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Convert to PNG data
|
|
610
|
+
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:thumbnailImage];
|
|
611
|
+
NSDictionary *properties = @{NSImageCompressionFactor: @0.8};
|
|
612
|
+
NSData *pngData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:properties];
|
|
613
|
+
|
|
614
|
+
if (!pngData) {
|
|
615
|
+
NSLog(@"Failed to convert image to PNG data");
|
|
616
|
+
CGImageRelease(thumbnailImage);
|
|
595
617
|
CGContextRelease(context);
|
|
618
|
+
CGImageRelease(displayImage);
|
|
619
|
+
CGColorSpaceRelease(colorSpace);
|
|
620
|
+
return env.Null();
|
|
596
621
|
}
|
|
597
622
|
|
|
623
|
+
// Convert to Base64
|
|
624
|
+
NSString *base64String = [pngData base64EncodedStringWithOptions:0];
|
|
625
|
+
std::string base64Std = [base64String UTF8String];
|
|
626
|
+
|
|
627
|
+
NSLog(@"Successfully created thumbnail with base64 length: %lu", (unsigned long)base64Std.length());
|
|
628
|
+
|
|
629
|
+
// Cleanup
|
|
630
|
+
CGImageRelease(thumbnailImage);
|
|
631
|
+
CGContextRelease(context);
|
|
598
632
|
CGColorSpaceRelease(colorSpace);
|
|
599
633
|
CGImageRelease(displayImage);
|
|
600
634
|
|
|
601
|
-
return env
|
|
635
|
+
return Napi::String::New(env, base64Std);
|
|
602
636
|
|
|
603
637
|
} @catch (NSException *exception) {
|
|
638
|
+
NSLog(@"Exception in GetDisplayThumbnail: %@", exception);
|
|
604
639
|
return env.Null();
|
|
605
640
|
}
|
|
606
641
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#ifndef SCREEN_CAPTURE_H
|
|
2
|
+
#define SCREEN_CAPTURE_H
|
|
3
|
+
|
|
4
|
+
#import <Foundation/Foundation.h>
|
|
5
|
+
#import <CoreGraphics/CoreGraphics.h>
|
|
6
|
+
|
|
7
|
+
@interface ScreenCapture : NSObject
|
|
8
|
+
|
|
9
|
+
+ (NSArray *)getAvailableDisplays;
|
|
10
|
+
+ (BOOL)captureDisplay:(CGDirectDisplayID)displayID
|
|
11
|
+
toFile:(NSString *)filePath
|
|
12
|
+
rect:(CGRect)rect
|
|
13
|
+
includeCursor:(BOOL)includeCursor;
|
|
14
|
+
+ (CGImageRef)createScreenshotFromDisplay:(CGDirectDisplayID)displayID
|
|
15
|
+
rect:(CGRect)rect;
|
|
16
|
+
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
#endif // SCREEN_CAPTURE_H
|