node-mac-recorder 2.22.15 → 2.22.17
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 +1 -0
- package/electron-safe-binding.gyp +1 -0
- package/electron-safe-index.js +196 -104
- package/index.js +8 -355
- package/lib/cursorCapture/displayInfo.js +110 -0
- package/lib/cursorCapture/polling.js +462 -0
- package/package.json +1 -1
- package/src/cursor_tracker.mm +42 -113
- package/src/electron_safe/cursor_tracker_electron.mm +34 -0
- package/src/text_input_ax_snapshot.h +3 -0
- package/src/text_input_ax_snapshot.mm +161 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#import "text_input_ax_snapshot.h"
|
|
2
|
+
#import <ApplicationServices/ApplicationServices.h>
|
|
3
|
+
|
|
4
|
+
static inline BOOL MRStringEqualsAny(NSString *value, NSArray<NSString *> *candidates) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return NO;
|
|
7
|
+
}
|
|
8
|
+
for (NSString *c in candidates) {
|
|
9
|
+
if ([value isEqualToString:c]) {
|
|
10
|
+
return YES;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return NO;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static NSString *MRCopyAttributeString(AXUIElementRef element, CFStringRef attribute) {
|
|
17
|
+
CFTypeRef value = NULL;
|
|
18
|
+
AXError err = AXUIElementCopyAttributeValue(element, attribute, &value);
|
|
19
|
+
if (err != kAXErrorSuccess || !value) {
|
|
20
|
+
return nil;
|
|
21
|
+
}
|
|
22
|
+
if (CFGetTypeID(value) != CFStringGetTypeID()) {
|
|
23
|
+
CFRelease(value);
|
|
24
|
+
return nil;
|
|
25
|
+
}
|
|
26
|
+
NSString *s = [NSString stringWithString:(__bridge NSString *)value];
|
|
27
|
+
CFRelease(value);
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static BOOL MRCopyAttributeBoolean(AXUIElementRef element, CFStringRef attribute, BOOL *outValue) {
|
|
32
|
+
CFTypeRef value = NULL;
|
|
33
|
+
AXError err = AXUIElementCopyAttributeValue(element, attribute, &value);
|
|
34
|
+
if (err != kAXErrorSuccess || !value) {
|
|
35
|
+
return NO;
|
|
36
|
+
}
|
|
37
|
+
BOOL ok = NO;
|
|
38
|
+
if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
|
|
39
|
+
*outValue = CFBooleanGetValue((CFBooleanRef)value);
|
|
40
|
+
ok = YES;
|
|
41
|
+
}
|
|
42
|
+
CFRelease(value);
|
|
43
|
+
return ok;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
NSDictionary *MRTextInputSnapshotDictionary(void) {
|
|
47
|
+
__block NSDictionary *result = nil;
|
|
48
|
+
void (^work)(void) = ^{
|
|
49
|
+
@autoreleasepool {
|
|
50
|
+
@try {
|
|
51
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
52
|
+
if (!systemWide) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
AXUIElementRef focusedElement = NULL;
|
|
57
|
+
AXError focusErr = AXUIElementCopyAttributeValue(
|
|
58
|
+
systemWide, kAXFocusedUIElementAttribute, (CFTypeRef *)&focusedElement);
|
|
59
|
+
|
|
60
|
+
if (focusErr != kAXErrorSuccess || !focusedElement) {
|
|
61
|
+
CFRelease(systemWide);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
NSString *role = MRCopyAttributeString(focusedElement, kAXRoleAttribute);
|
|
66
|
+
BOOL isEditable = NO;
|
|
67
|
+
MRCopyAttributeBoolean(focusedElement, CFSTR("AXEditable"), &isEditable);
|
|
68
|
+
|
|
69
|
+
CFTypeRef selectedRangeValue = NULL;
|
|
70
|
+
AXError rangeProbeErr = AXUIElementCopyAttributeValue(
|
|
71
|
+
focusedElement, CFSTR("AXSelectedTextRange"), (CFTypeRef *)&selectedRangeValue);
|
|
72
|
+
BOOL hasTextRange = (rangeProbeErr == kAXErrorSuccess && selectedRangeValue != NULL);
|
|
73
|
+
|
|
74
|
+
BOOL isStandardTextRole = MRStringEqualsAny(role, @[
|
|
75
|
+
@"AXTextField", @"AXTextArea", @"AXTextView",
|
|
76
|
+
@"AXTextEditor", @"AXSearchField",
|
|
77
|
+
@"AXComboBox"
|
|
78
|
+
]);
|
|
79
|
+
BOOL isWebAreaWithCaret = [role isEqualToString:@"AXWebArea"] && hasTextRange;
|
|
80
|
+
BOOL isTextField = isStandardTextRole || isEditable || isWebAreaWithCaret || hasTextRange;
|
|
81
|
+
|
|
82
|
+
if (!isTextField) {
|
|
83
|
+
if (selectedRangeValue) {
|
|
84
|
+
CFRelease(selectedRangeValue);
|
|
85
|
+
}
|
|
86
|
+
CFRelease(focusedElement);
|
|
87
|
+
CFRelease(systemWide);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
CGPoint inputOrigin = CGPointZero;
|
|
92
|
+
CGSize inputSize = CGSizeZero;
|
|
93
|
+
AXValueRef positionValue = NULL;
|
|
94
|
+
AXValueRef sizeValue = NULL;
|
|
95
|
+
|
|
96
|
+
AXUIElementCopyAttributeValue(focusedElement, kAXPositionAttribute, (CFTypeRef *)&positionValue);
|
|
97
|
+
AXUIElementCopyAttributeValue(focusedElement, kAXSizeAttribute, (CFTypeRef *)&sizeValue);
|
|
98
|
+
|
|
99
|
+
if (positionValue) {
|
|
100
|
+
AXValueGetValue(positionValue, kAXValueTypeCGPoint, &inputOrigin);
|
|
101
|
+
CFRelease(positionValue);
|
|
102
|
+
}
|
|
103
|
+
if (sizeValue) {
|
|
104
|
+
AXValueGetValue(sizeValue, kAXValueTypeCGSize, &inputSize);
|
|
105
|
+
CFRelease(sizeValue);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
CGPoint caretPos = CGPointMake(inputOrigin.x, inputOrigin.y);
|
|
109
|
+
BOOL hasCaretPos = NO;
|
|
110
|
+
|
|
111
|
+
if (selectedRangeValue) {
|
|
112
|
+
CFTypeRef boundsValue = NULL;
|
|
113
|
+
AXError boundsErr = AXUIElementCopyParameterizedAttributeValue(
|
|
114
|
+
focusedElement, CFSTR("AXBoundsForRange"),
|
|
115
|
+
selectedRangeValue, &boundsValue);
|
|
116
|
+
|
|
117
|
+
if (boundsErr == kAXErrorSuccess && boundsValue) {
|
|
118
|
+
CGRect caretBounds = CGRectZero;
|
|
119
|
+
if (AXValueGetValue((AXValueRef)boundsValue, kAXValueTypeCGRect, &caretBounds)) {
|
|
120
|
+
caretPos = CGPointMake(
|
|
121
|
+
caretBounds.origin.x,
|
|
122
|
+
caretBounds.origin.y + caretBounds.size.height / 2.0
|
|
123
|
+
);
|
|
124
|
+
hasCaretPos = YES;
|
|
125
|
+
}
|
|
126
|
+
CFRelease(boundsValue);
|
|
127
|
+
}
|
|
128
|
+
CFRelease(selectedRangeValue);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!hasCaretPos) {
|
|
132
|
+
caretPos = CGPointMake(inputOrigin.x + 4, inputOrigin.y + inputSize.height / 2.0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
result = @{
|
|
136
|
+
@"caretX": @((int)lround(caretPos.x)),
|
|
137
|
+
@"caretY": @((int)lround(caretPos.y)),
|
|
138
|
+
@"inputFrame": @{
|
|
139
|
+
@"x": @((int)lround(inputOrigin.x)),
|
|
140
|
+
@"y": @((int)lround(inputOrigin.y)),
|
|
141
|
+
@"width": @((int)lround(inputSize.width)),
|
|
142
|
+
@"height": @((int)lround(inputSize.height))
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
CFRelease(focusedElement);
|
|
147
|
+
CFRelease(systemWide);
|
|
148
|
+
} @catch (__unused NSException *e) {
|
|
149
|
+
result = nil;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if ([NSThread isMainThread]) {
|
|
155
|
+
work();
|
|
156
|
+
} else {
|
|
157
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|