node-mac-recorder 2.22.24 → 2.22.33
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/MultiWindowRecorder.js +1 -0
- package/binding.gyp +0 -1
- package/electron-safe-binding.gyp +0 -1
- package/electron-safe-index.js +104 -196
- package/index.js +369 -9
- package/package.json +4 -2
- package/scripts/cursor-type-live.js +32 -0
- package/scripts/cursor-types-15s-test.js +122 -0
- package/src/avfoundation_recorder.mm +1 -2
- package/src/cursor_tracker.mm +385 -137
- package/src/electron_safe/cursor_tracker_electron.mm +36 -38
- package/src/electron_safe/window_selector_electron.mm +5 -2
- package/src/screen_capture_kit.mm +11 -17
- package/src/window_selector.mm +5 -2
- package/lib/cursorCapture/displayInfo.js +0 -110
- package/lib/cursorCapture/polling.js +0 -585
- package/src/text_input_ax_snapshot.h +0 -3
- package/src/text_input_ax_snapshot.mm +0 -161
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
#import <CoreGraphics/CoreGraphics.h>
|
|
3
3
|
#import <AppKit/AppKit.h>
|
|
4
4
|
#import "../logging.h"
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
static NSCursor *CursorFactoryNamed(NSString *name) {
|
|
7
|
+
if (!name || [name length] == 0) return nil;
|
|
8
|
+
SEL sel = NSSelectorFromString(name);
|
|
9
|
+
if (!sel || ![NSCursor respondsToSelector:sel]) return nil;
|
|
10
|
+
IMP imp = [NSCursor methodForSelector:sel];
|
|
11
|
+
if (!imp) return nil;
|
|
12
|
+
typedef NSCursor *(*CursorFactoryFunc)(id, SEL);
|
|
13
|
+
CursorFactoryFunc fn = (CursorFactoryFunc)imp;
|
|
14
|
+
return fn([NSCursor class], sel);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static BOOL CursorEqualsFactoryNamed(NSCursor *cursor, NSString *factoryName) {
|
|
18
|
+
NSCursor *ref = CursorFactoryNamed(factoryName);
|
|
19
|
+
return ref != nil && cursor == ref;
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
// Thread-safe cursor tracking for Electron
|
|
8
23
|
static dispatch_queue_t g_cursorQueue = nil;
|
|
@@ -24,18 +39,34 @@ static NSString* MapCursorToType(NSCursor *cursor) {
|
|
|
24
39
|
if (cursor == [NSCursor pointingHandCursor]) return @"pointer";
|
|
25
40
|
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
26
41
|
if (cursor == [NSCursor resizeLeftRightCursor] ||
|
|
27
|
-
[NSCursor
|
|
28
|
-
[NSCursor
|
|
42
|
+
([NSCursor respondsToSelector:@selector(resizeLeftCursor)] && cursor == [NSCursor resizeLeftCursor]) ||
|
|
43
|
+
([NSCursor respondsToSelector:@selector(resizeRightCursor)] && cursor == [NSCursor resizeRightCursor])) {
|
|
29
44
|
return @"col-resize";
|
|
30
45
|
}
|
|
31
46
|
}
|
|
32
47
|
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
33
48
|
if (cursor == [NSCursor resizeUpDownCursor] ||
|
|
34
|
-
[NSCursor
|
|
35
|
-
[NSCursor
|
|
49
|
+
([NSCursor respondsToSelector:@selector(resizeUpCursor)] && cursor == [NSCursor resizeUpCursor]) ||
|
|
50
|
+
([NSCursor respondsToSelector:@selector(resizeDownCursor)] && cursor == [NSCursor resizeDownCursor])) {
|
|
36
51
|
return @"ns-resize";
|
|
37
52
|
}
|
|
38
53
|
}
|
|
54
|
+
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthWestSouthEastCursor")) {
|
|
55
|
+
return @"nwse-resize";
|
|
56
|
+
}
|
|
57
|
+
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthEastSouthWestCursor")) {
|
|
58
|
+
return @"nesw-resize";
|
|
59
|
+
}
|
|
60
|
+
if (@available(macOS 15.0, *)) {
|
|
61
|
+
if ([NSCursor respondsToSelector:@selector(columnResizeCursor)] &&
|
|
62
|
+
cursor == [NSCursor columnResizeCursor]) {
|
|
63
|
+
return @"col-resize";
|
|
64
|
+
}
|
|
65
|
+
if ([NSCursor respondsToSelector:@selector(rowResizeCursor)] &&
|
|
66
|
+
cursor == [NSCursor rowResizeCursor]) {
|
|
67
|
+
return @"row-resize";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
if ([NSCursor respondsToSelector:@selector(openHandCursor)] && cursor == [NSCursor openHandCursor]) return @"grab";
|
|
40
71
|
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] && cursor == [NSCursor closedHandCursor]) return @"grabbing";
|
|
41
72
|
if ([NSCursor respondsToSelector:@selector(crosshairCursor)] && cursor == [NSCursor crosshairCursor]) return @"crosshair";
|
|
@@ -93,45 +124,12 @@ Napi::Value GetCursorPositionElectronSafe(const Napi::CallbackInfo& info) {
|
|
|
93
124
|
}
|
|
94
125
|
}
|
|
95
126
|
|
|
96
|
-
static Napi::Value DictToNapiTextInputSnapshotElectron(Napi::Env env, NSDictionary *snap) {
|
|
97
|
-
Napi::Object o = Napi::Object::New(env);
|
|
98
|
-
NSNumber *cx = snap[@"caretX"];
|
|
99
|
-
NSNumber *cy = snap[@"caretY"];
|
|
100
|
-
o.Set("caretX", Napi::Number::New(env, cx ? [cx doubleValue] : 0));
|
|
101
|
-
o.Set("caretY", Napi::Number::New(env, cy ? [cy doubleValue] : 0));
|
|
102
|
-
NSDictionary *frame = snap[@"inputFrame"];
|
|
103
|
-
Napi::Object fo = Napi::Object::New(env);
|
|
104
|
-
if ([frame isKindOfClass:[NSDictionary class]]) {
|
|
105
|
-
fo.Set("x", Napi::Number::New(env, [frame[@"x"] doubleValue]));
|
|
106
|
-
fo.Set("y", Napi::Number::New(env, [frame[@"y"] doubleValue]));
|
|
107
|
-
fo.Set("width", Napi::Number::New(env, [frame[@"width"] doubleValue]));
|
|
108
|
-
fo.Set("height", Napi::Number::New(env, [frame[@"height"] doubleValue]));
|
|
109
|
-
}
|
|
110
|
-
o.Set("inputFrame", fo);
|
|
111
|
-
return o;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
Napi::Value GetTextInputSnapshotElectronSafe(const Napi::CallbackInfo& info) {
|
|
115
|
-
Napi::Env env = info.Env();
|
|
116
|
-
@try {
|
|
117
|
-
NSDictionary *snap = MRTextInputSnapshotDictionary();
|
|
118
|
-
if (!snap) {
|
|
119
|
-
return env.Null();
|
|
120
|
-
}
|
|
121
|
-
return DictToNapiTextInputSnapshotElectron(env, snap);
|
|
122
|
-
} @catch (NSException *e) {
|
|
123
|
-
NSLog(@"❌ getTextInputSnapshot: %@", e.reason);
|
|
124
|
-
return env.Null();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
127
|
// Initialize cursor tracker module
|
|
129
128
|
Napi::Object InitCursorTrackerElectron(Napi::Env env, Napi::Object exports) {
|
|
130
129
|
@try {
|
|
131
130
|
initializeCursorQueue();
|
|
132
131
|
|
|
133
132
|
exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPositionElectronSafe));
|
|
134
|
-
exports.Set("getTextInputSnapshot", Napi::Function::New(env, GetTextInputSnapshotElectronSafe));
|
|
135
133
|
|
|
136
134
|
MRLog(@"✅ Electron-safe cursor tracker initialized");
|
|
137
135
|
return exports;
|
|
@@ -13,9 +13,12 @@ static void initializeWindowQueue() {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
static BOOL ShouldAllowElectronWindows(void) {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// NSProcessInfo.environment snapshot'lanır ve runtime'da process.env değişikliklerini yansıtmaz.
|
|
17
|
+
// getenv() libc üzerinden her seferinde güncel değeri okur — menüden toggle'lanabilsin diye.
|
|
18
|
+
const char *raw = getenv("CREAVIT_ALLOW_ELECTRON_WINDOWS");
|
|
19
|
+
if (!raw) return NO;
|
|
18
20
|
|
|
21
|
+
NSString *flag = [NSString stringWithUTF8String:raw];
|
|
19
22
|
NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
20
23
|
return [normalized isEqualToString:@"1"] ||
|
|
21
24
|
[normalized isEqualToString:@"true"] ||
|
|
@@ -342,23 +342,17 @@ static void FinishWriter(AVAssetWriter *writer, AVAssetWriterInput *input) {
|
|
|
342
342
|
if (!writer) {
|
|
343
343
|
return;
|
|
344
344
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Status 0 (Unknown) veya diğer durumlarda crash eder
|
|
348
|
-
if (input && writer.status == AVAssetWriterStatusWriting) {
|
|
345
|
+
|
|
346
|
+
if (input) {
|
|
349
347
|
[input markAsFinished];
|
|
350
348
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
dispatch_semaphore_wait(semaphore, timeout);
|
|
359
|
-
} else if (writer.status == AVAssetWriterStatusFailed) {
|
|
360
|
-
NSLog(@"⚠️ Writer already failed: %@", writer.error);
|
|
361
|
-
}
|
|
349
|
+
|
|
350
|
+
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
|
351
|
+
[writer finishWritingWithCompletionHandler:^{
|
|
352
|
+
dispatch_semaphore_signal(semaphore);
|
|
353
|
+
}];
|
|
354
|
+
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
|
|
355
|
+
dispatch_semaphore_wait(semaphore, timeout);
|
|
362
356
|
}
|
|
363
357
|
|
|
364
358
|
static void CleanupWriters(void) {
|
|
@@ -379,10 +373,10 @@ static void CleanupWriters(void) {
|
|
|
379
373
|
}
|
|
380
374
|
|
|
381
375
|
if (g_audioWriter) {
|
|
382
|
-
if (g_systemAudioInput
|
|
376
|
+
if (g_systemAudioInput) {
|
|
383
377
|
[g_systemAudioInput markAsFinished];
|
|
384
378
|
}
|
|
385
|
-
if (g_microphoneAudioInput
|
|
379
|
+
if (g_microphoneAudioInput) {
|
|
386
380
|
[g_microphoneAudioInput markAsFinished];
|
|
387
381
|
}
|
|
388
382
|
FinishWriter(g_audioWriter, nil);
|
package/src/window_selector.mm
CHANGED
|
@@ -57,9 +57,12 @@ static NSTimer *g_screenTrackingTimer = nil;
|
|
|
57
57
|
static NSInteger g_currentActiveScreenIndex = -1;
|
|
58
58
|
|
|
59
59
|
static bool shouldAllowElectronWindows() {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// NSProcessInfo.environment snapshot'lanır ve runtime değişikliklerini yansıtmaz.
|
|
61
|
+
// getenv() ile process.env güncellemelerini her seferinde okuyoruz.
|
|
62
|
+
const char *raw = getenv("CREAVIT_ALLOW_ELECTRON_WINDOWS");
|
|
63
|
+
if (!raw) return false;
|
|
62
64
|
|
|
65
|
+
NSString *flag = [NSString stringWithUTF8String:raw];
|
|
63
66
|
NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
64
67
|
return [normalized isEqualToString:@"1"] ||
|
|
65
68
|
[normalized isEqualToString:@"true"] ||
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
async function resolveCursorDisplayInfo(recorder, options) {
|
|
4
|
-
if (options.videoRelative && options.displayInfo) {
|
|
5
|
-
let videoOffsetX = 0;
|
|
6
|
-
let videoOffsetY = 0;
|
|
7
|
-
let videoWidth =
|
|
8
|
-
options.displayInfo.width || options.displayInfo.logicalWidth;
|
|
9
|
-
let videoHeight =
|
|
10
|
-
options.displayInfo.height || options.displayInfo.logicalHeight;
|
|
11
|
-
|
|
12
|
-
if (options.recordingType === "window" && options.windowId) {
|
|
13
|
-
if (options.captureArea) {
|
|
14
|
-
videoOffsetX = options.captureArea.x;
|
|
15
|
-
videoOffsetY = options.captureArea.y;
|
|
16
|
-
videoWidth = options.captureArea.width;
|
|
17
|
-
videoHeight = options.captureArea.height;
|
|
18
|
-
}
|
|
19
|
-
} else if (options.recordingType === "area" && options.captureArea) {
|
|
20
|
-
videoOffsetX = options.captureArea.x;
|
|
21
|
-
videoOffsetY = options.captureArea.y;
|
|
22
|
-
videoWidth = options.captureArea.width;
|
|
23
|
-
videoHeight = options.captureArea.height;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
recorder.cursorDisplayInfo = {
|
|
27
|
-
displayId: options.displayInfo.displayId || options.displayInfo.id,
|
|
28
|
-
displayX: options.displayInfo.x || 0,
|
|
29
|
-
displayY: options.displayInfo.y || 0,
|
|
30
|
-
displayWidth:
|
|
31
|
-
options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
32
|
-
displayHeight:
|
|
33
|
-
options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
34
|
-
videoOffsetX,
|
|
35
|
-
videoOffsetY,
|
|
36
|
-
videoWidth,
|
|
37
|
-
videoHeight,
|
|
38
|
-
videoRelative: true,
|
|
39
|
-
recordingType: options.recordingType || "display",
|
|
40
|
-
captureArea: options.captureArea,
|
|
41
|
-
windowId: options.windowId,
|
|
42
|
-
multiWindowBounds: options.multiWindowBounds || null,
|
|
43
|
-
};
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (recorder.recordingDisplayInfo) {
|
|
48
|
-
recorder.cursorDisplayInfo = {
|
|
49
|
-
...recorder.recordingDisplayInfo,
|
|
50
|
-
displayX: recorder.recordingDisplayInfo.x || 0,
|
|
51
|
-
displayY: recorder.recordingDisplayInfo.y || 0,
|
|
52
|
-
displayWidth:
|
|
53
|
-
recorder.recordingDisplayInfo.width ||
|
|
54
|
-
recorder.recordingDisplayInfo.logicalWidth,
|
|
55
|
-
displayHeight:
|
|
56
|
-
recorder.recordingDisplayInfo.height ||
|
|
57
|
-
recorder.recordingDisplayInfo.logicalHeight,
|
|
58
|
-
videoOffsetX: 0,
|
|
59
|
-
videoOffsetY: 0,
|
|
60
|
-
videoWidth:
|
|
61
|
-
recorder.recordingDisplayInfo.width ||
|
|
62
|
-
recorder.recordingDisplayInfo.logicalWidth,
|
|
63
|
-
videoHeight:
|
|
64
|
-
recorder.recordingDisplayInfo.height ||
|
|
65
|
-
recorder.recordingDisplayInfo.logicalHeight,
|
|
66
|
-
videoRelative: true,
|
|
67
|
-
recordingType: options.recordingType || "display",
|
|
68
|
-
multiWindowBounds: options.multiWindowBounds || null,
|
|
69
|
-
};
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const displays = await recorder.getDisplays();
|
|
75
|
-
const mainDisplay =
|
|
76
|
-
displays.find((d) => d.isPrimary) || displays[0];
|
|
77
|
-
if (mainDisplay) {
|
|
78
|
-
let w = mainDisplay.width;
|
|
79
|
-
let h = mainDisplay.height;
|
|
80
|
-
const res = mainDisplay.resolution;
|
|
81
|
-
if ((w == null || h == null) && res) {
|
|
82
|
-
const parts = String(res).split("x");
|
|
83
|
-
if (w == null) {
|
|
84
|
-
w = parseInt(parts[0], 10);
|
|
85
|
-
}
|
|
86
|
-
if (h == null) {
|
|
87
|
-
h = parseInt(parts[1], 10);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!Number.isFinite(w) || w <= 0) {
|
|
91
|
-
w = 1920;
|
|
92
|
-
}
|
|
93
|
-
if (!Number.isFinite(h) || h <= 0) {
|
|
94
|
-
h = 1080;
|
|
95
|
-
}
|
|
96
|
-
recorder.cursorDisplayInfo = {
|
|
97
|
-
displayId: mainDisplay.id,
|
|
98
|
-
x: mainDisplay.x,
|
|
99
|
-
y: mainDisplay.y,
|
|
100
|
-
width: w,
|
|
101
|
-
height: h,
|
|
102
|
-
multiWindowBounds: options.multiWindowBounds || null,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
recorder.cursorDisplayInfo = null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
module.exports = { resolveCursorDisplayInfo };
|