node-mac-recorder 2.21.43 → 2.21.44
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 +8 -1
- package/analyze-cursors.js +90 -0
- package/build-seed-map.js +219 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/cursor_tracker.mm +1098 -80
|
@@ -33,7 +33,14 @@
|
|
|
33
33
|
"Bash(python3:*)",
|
|
34
34
|
"Bash(git checkout:*)",
|
|
35
35
|
"Bash(1)",
|
|
36
|
-
"Bash(for f in /Users/onur/Downloads/\"Creavit Studio\"/temp_camera_*.mov)"
|
|
36
|
+
"Bash(for f in /Users/onur/Downloads/\"Creavit Studio\"/temp_camera_*.mov)",
|
|
37
|
+
"Bash(timeout 10 node:*)",
|
|
38
|
+
"Bash(timeout 8 node:*)",
|
|
39
|
+
"Bash(timeout 5 node:*)",
|
|
40
|
+
"Bash(open:*)",
|
|
41
|
+
"Bash(timeout 3 node build-seed-map.js:*)",
|
|
42
|
+
"Bash(echo:*)",
|
|
43
|
+
"Bash(timeout 3 node:*)"
|
|
37
44
|
],
|
|
38
45
|
"deny": [],
|
|
39
46
|
"ask": []
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
// cursor-nscursor-mapping.json'u oku
|
|
4
|
+
const mapping = JSON.parse(fs.readFileSync('cursor-nscursor-mapping.json', 'utf8'));
|
|
5
|
+
|
|
6
|
+
console.log('\n📊 CURSOR DETECTION ANALYSIS\n');
|
|
7
|
+
console.log('═'.repeat(100));
|
|
8
|
+
|
|
9
|
+
// Group by image size
|
|
10
|
+
const byImageSize = {};
|
|
11
|
+
Object.keys(mapping.cursorMapping).forEach(cssType => {
|
|
12
|
+
const data = mapping.cursorMapping[cssType];
|
|
13
|
+
if (!data.imageSize) return;
|
|
14
|
+
|
|
15
|
+
const key = `${data.imageSize.width}x${data.imageSize.height}`;
|
|
16
|
+
if (!byImageSize[key]) {
|
|
17
|
+
byImageSize[key] = [];
|
|
18
|
+
}
|
|
19
|
+
byImageSize[key].push({
|
|
20
|
+
cssType,
|
|
21
|
+
hotspot: data.hotspot,
|
|
22
|
+
detected: data.detection.final
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log('\n🔍 CURSORS GROUPED BY IMAGE SIZE:\n');
|
|
27
|
+
Object.keys(byImageSize).sort().forEach(size => {
|
|
28
|
+
const cursors = byImageSize[size];
|
|
29
|
+
console.log(`\n${size} (${cursors.length} cursor${cursors.length > 1 ? 's' : ''}):`);
|
|
30
|
+
cursors.forEach(c => {
|
|
31
|
+
const match = c.cssType === c.detected ? '✅' : '❌';
|
|
32
|
+
console.log(` ${match} ${c.cssType.padEnd(20)} → detected: ${c.detected.padEnd(15)} | hotspot: (${c.hotspot.relativeX.toFixed(3)}, ${c.hotspot.relativeY.toFixed(3)})`);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log('\n\n');
|
|
37
|
+
console.log('═'.repeat(100));
|
|
38
|
+
console.log('\n💡 RECOMMENDATIONS:\n');
|
|
39
|
+
|
|
40
|
+
console.log('\n1. **28x40 cursors** (hepsi aynı görünüyor):');
|
|
41
|
+
console.log(' - auto, default, context-menu, progress, wait, copy, no-drop, not-allowed');
|
|
42
|
+
console.log(' - Çözüm: NSCursor pointer comparison veya description string parsing');
|
|
43
|
+
|
|
44
|
+
console.log('\n2. **24x24 cursors**:');
|
|
45
|
+
console.log(' - crosshair vs move/all-scroll');
|
|
46
|
+
console.log(' - Hotspot farklı: crosshair=(0.458, 0.458), move=(0.5, 0.5)');
|
|
47
|
+
|
|
48
|
+
console.log('\n3. **32x32 cursors**:');
|
|
49
|
+
console.log(' - pointer vs grab/grabbing');
|
|
50
|
+
console.log(' - Hotspot farklı: pointer=(0.406, 0.25), grab=(0.5, 0.531)');
|
|
51
|
+
console.log(' - grab vs grabbing: description içinde "closed" var mı kontrol et');
|
|
52
|
+
|
|
53
|
+
console.log('\n4. **22x22 cursors** (tüm diagonal resize\'lar):');
|
|
54
|
+
console.log(' - ne-resize, nw-resize, se-resize, sw-resize, nesw-resize, nwse-resize');
|
|
55
|
+
console.log(' - Hepsi görsel olarak aynı - NSCursor API ile ayırt edilemez!');
|
|
56
|
+
|
|
57
|
+
console.log('\n5. **18x18 cursors**:');
|
|
58
|
+
console.log(' - help vs cell');
|
|
59
|
+
console.log(' - Ayırt edilemez - ikisi de aynı image');
|
|
60
|
+
|
|
61
|
+
console.log('\n\n');
|
|
62
|
+
console.log('═'.repeat(100));
|
|
63
|
+
console.log('\n🎯 EXPECTED ACCURACY:\n');
|
|
64
|
+
|
|
65
|
+
const total = Object.keys(mapping.cursorMapping).length;
|
|
66
|
+
const correct = Object.keys(mapping.cursorMapping).filter(cssType => {
|
|
67
|
+
const data = mapping.cursorMapping[cssType];
|
|
68
|
+
return data.detection.final === cssType;
|
|
69
|
+
}).length;
|
|
70
|
+
|
|
71
|
+
console.log(`Current: ${correct}/${total} = ${((correct/total)*100).toFixed(1)}%`);
|
|
72
|
+
|
|
73
|
+
// Realistic best case
|
|
74
|
+
const impossible = [
|
|
75
|
+
'auto', // same as default
|
|
76
|
+
'context-menu', 'progress', 'wait', 'copy', 'no-drop', 'not-allowed', // all 28x40 with same hotspot
|
|
77
|
+
'move', 'all-scroll', // same 24x24
|
|
78
|
+
'cell', // same as help
|
|
79
|
+
'grabbing', // same as grab
|
|
80
|
+
'n-resize', 's-resize', // same as ns-resize
|
|
81
|
+
'e-resize', 'w-resize', // same as ew-resize
|
|
82
|
+
'ne-resize', 'nw-resize', 'se-resize', 'sw-resize', 'nesw-resize', // all same as nwse-resize
|
|
83
|
+
'zoom-out' // same as zoom-in
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const realistic = total - impossible.length;
|
|
87
|
+
console.log(`Best realistic: ${realistic}/${total} = ${((realistic/total)*100).toFixed(1)}%`);
|
|
88
|
+
console.log(`\nNote: ${impossible.length} cursors are visually identical to others and cannot be distinguished by image alone.`);
|
|
89
|
+
|
|
90
|
+
console.log('\n');
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const MacRecorder = require('./index.js');
|
|
2
|
+
const recorder = new MacRecorder();
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
console.log('\n🎯 SEED MAPPING BUILDER\n');
|
|
6
|
+
console.log('═'.repeat(100));
|
|
7
|
+
console.log('\n📋 Bu tool tüm cursor tiplerinin seed mapping\'ini oluşturur.\n');
|
|
8
|
+
|
|
9
|
+
// Tüm CSS cursor tipleri (HTML'deki sırayla)
|
|
10
|
+
const cursorTypes = [
|
|
11
|
+
"auto",
|
|
12
|
+
"default",
|
|
13
|
+
"none",
|
|
14
|
+
"context-menu",
|
|
15
|
+
"help",
|
|
16
|
+
"pointer",
|
|
17
|
+
"progress",
|
|
18
|
+
"wait",
|
|
19
|
+
"cell",
|
|
20
|
+
"crosshair",
|
|
21
|
+
"text",
|
|
22
|
+
"vertical-text",
|
|
23
|
+
"alias",
|
|
24
|
+
"copy",
|
|
25
|
+
"move",
|
|
26
|
+
"no-drop",
|
|
27
|
+
"not-allowed",
|
|
28
|
+
"grab",
|
|
29
|
+
"grabbing",
|
|
30
|
+
"all-scroll",
|
|
31
|
+
"col-resize",
|
|
32
|
+
"row-resize",
|
|
33
|
+
"n-resize",
|
|
34
|
+
"e-resize",
|
|
35
|
+
"s-resize",
|
|
36
|
+
"w-resize",
|
|
37
|
+
"ne-resize",
|
|
38
|
+
"nw-resize",
|
|
39
|
+
"se-resize",
|
|
40
|
+
"sw-resize",
|
|
41
|
+
"ew-resize",
|
|
42
|
+
"ns-resize",
|
|
43
|
+
"nesw-resize",
|
|
44
|
+
"nwse-resize",
|
|
45
|
+
"zoom-in",
|
|
46
|
+
"zoom-out"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
let currentIndex = 0;
|
|
50
|
+
let seedMapping = {}; // cursorType -> seed
|
|
51
|
+
let isWaitingForClick = false;
|
|
52
|
+
|
|
53
|
+
console.log(`\n📝 ${cursorTypes.length} cursor tipi için seed toplanacak:\n`);
|
|
54
|
+
cursorTypes.forEach((cursor, index) => {
|
|
55
|
+
console.log(` ${(index + 1).toString().padStart(2)}. ${cursor}`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log('\n' + '═'.repeat(100));
|
|
59
|
+
console.log('\n💡 Nasıl Kullanılır:');
|
|
60
|
+
console.log(' 1. test-all-cursors.html dosyası tarayıcıda açık olmalı');
|
|
61
|
+
console.log(' 2. Bu script size hangi cursor\'a tıklamanız gerektiğini söyleyecek');
|
|
62
|
+
console.log(' 3. HTML\'deki o cursor kutusuna TIKLAYIN');
|
|
63
|
+
console.log(' 4. Seed otomatik kaydedilecek ve bir sonrakine geçilecek');
|
|
64
|
+
console.log(' 5. Tüm cursor\'lar bitince seed-mapping.json oluşturulacak\n');
|
|
65
|
+
console.log('═'.repeat(100));
|
|
66
|
+
|
|
67
|
+
function showNextCursor() {
|
|
68
|
+
if (currentIndex >= cursorTypes.length) {
|
|
69
|
+
finishMapping();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const cursorType = cursorTypes[currentIndex];
|
|
74
|
+
console.log(`\n\n🎯 [${currentIndex + 1}/${cursorTypes.length}] "${cursorType}" cursor'una tıkla`);
|
|
75
|
+
console.log('👆 HTML\'de bu cursor kutusunu bul ve TIKLA...\n');
|
|
76
|
+
isWaitingForClick = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function recordSeedOnClick(pos) {
|
|
80
|
+
if (!isWaitingForClick) return;
|
|
81
|
+
|
|
82
|
+
isWaitingForClick = false;
|
|
83
|
+
const cursorType = cursorTypes[currentIndex];
|
|
84
|
+
const seed = pos.seed || -1;
|
|
85
|
+
|
|
86
|
+
console.log(`✅ Kaydedildi: ${cursorType.padEnd(20)} → seed: ${seed}`);
|
|
87
|
+
|
|
88
|
+
// Mapping'e ekle
|
|
89
|
+
seedMapping[cursorType] = seed;
|
|
90
|
+
|
|
91
|
+
currentIndex++;
|
|
92
|
+
|
|
93
|
+
// 200ms bekle sonraki cursor'a geç
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
showNextCursor();
|
|
96
|
+
}, 200);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function finishMapping() {
|
|
100
|
+
console.log('\n\n🎉 TÜM CURSOR TİPLERİ İÇİN SEED TOPLANDI!\n');
|
|
101
|
+
console.log('═'.repeat(100));
|
|
102
|
+
console.log(`\n✅ ${Object.keys(seedMapping).length} cursor tipi kaydedildi.\n`);
|
|
103
|
+
console.log('═'.repeat(100));
|
|
104
|
+
|
|
105
|
+
// Reverse mapping oluştur: seed -> cursorType
|
|
106
|
+
const seedToType = {};
|
|
107
|
+
Object.keys(seedMapping).forEach(cursorType => {
|
|
108
|
+
const seed = seedMapping[cursorType];
|
|
109
|
+
if (seed > 0) {
|
|
110
|
+
// Eğer bu seed zaten başka bir cursor'a atanmışsa, not ekle
|
|
111
|
+
if (seedToType[seed]) {
|
|
112
|
+
console.log(`⚠️ Seed collision: ${seed} both ${seedToType[seed]} and ${cursorType}`);
|
|
113
|
+
// İlk gördüğümüzü tut, ama not ekle
|
|
114
|
+
if (!seedToType[seed].includes('/')) {
|
|
115
|
+
seedToType[seed] = `${seedToType[seed]}/${cursorType}`;
|
|
116
|
+
} else {
|
|
117
|
+
seedToType[seed] += `/${cursorType}`;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
seedToType[seed] = cursorType;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log('\n📊 SEED MAPPING ÖZET:\n');
|
|
126
|
+
console.log('Cursor Type -> Seed');
|
|
127
|
+
console.log('─'.repeat(100));
|
|
128
|
+
Object.keys(seedMapping).sort().forEach(cursorType => {
|
|
129
|
+
const seed = seedMapping[cursorType];
|
|
130
|
+
console.log(`${cursorType.padEnd(20)} → ${seed}`);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
console.log('\n\n📊 REVERSE MAPPING (Seed -> Cursor):\n');
|
|
134
|
+
console.log('Seed -> Cursor Type');
|
|
135
|
+
console.log('─'.repeat(100));
|
|
136
|
+
Object.keys(seedToType).sort((a, b) => parseInt(a) - parseInt(b)).forEach(seed => {
|
|
137
|
+
console.log(`${seed.padEnd(10)} → ${seedToType[seed]}`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Dosyaya kaydet
|
|
141
|
+
const output = {
|
|
142
|
+
metadata: {
|
|
143
|
+
timestamp: new Date().toISOString(),
|
|
144
|
+
totalCursorTypes: Object.keys(seedMapping).length,
|
|
145
|
+
uniqueSeeds: Object.keys(seedToType).length
|
|
146
|
+
},
|
|
147
|
+
cursorToSeed: seedMapping,
|
|
148
|
+
seedToCursor: seedToType
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
fs.writeFileSync('seed-mapping.json', JSON.stringify(output, null, 2));
|
|
152
|
+
console.log('\n✅ Mapping "seed-mapping.json" dosyasına kaydedildi!\n');
|
|
153
|
+
|
|
154
|
+
// C++ için kod üret
|
|
155
|
+
console.log('\n═'.repeat(100));
|
|
156
|
+
console.log('\n📝 C++ KODU (cursor_tracker.mm için):\n');
|
|
157
|
+
console.log('static NSString* cursorTypeFromSeed(int seed) {');
|
|
158
|
+
console.log(' switch(seed) {');
|
|
159
|
+
|
|
160
|
+
Object.keys(seedToType).sort((a, b) => parseInt(a) - parseInt(b)).forEach(seed => {
|
|
161
|
+
const cursorType = seedToType[seed];
|
|
162
|
+
// Eğer collision varsa (örn: "auto/default"), ilkini al
|
|
163
|
+
const primaryType = cursorType.split('/')[0];
|
|
164
|
+
console.log(` case ${seed}: return @"${primaryType}";`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log(' default: return nil;');
|
|
168
|
+
console.log(' }');
|
|
169
|
+
console.log('}\n');
|
|
170
|
+
console.log('═'.repeat(100));
|
|
171
|
+
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Mouse click monitoring
|
|
176
|
+
let lastClickTime = 0;
|
|
177
|
+
let monitoringInterval = null;
|
|
178
|
+
|
|
179
|
+
console.log('\n⏳ test-all-cursors.html açık mı? 3 saniye sonra başlıyoruz...\n');
|
|
180
|
+
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
console.log('🚀 TEST BAŞLADI! HTML\'deki cursor kutularına tıklayın!\n');
|
|
183
|
+
console.log('═'.repeat(100));
|
|
184
|
+
showNextCursor();
|
|
185
|
+
|
|
186
|
+
// Mouse click'i sürekli kontrol et
|
|
187
|
+
monitoringInterval = setInterval(() => {
|
|
188
|
+
try {
|
|
189
|
+
const pos = recorder.getCursorPosition();
|
|
190
|
+
|
|
191
|
+
// Mouse down eventi
|
|
192
|
+
if (pos && pos.eventType === 'mousedown') {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
|
|
195
|
+
// Debounce - 300ms
|
|
196
|
+
if (now - lastClickTime > 300) {
|
|
197
|
+
lastClickTime = now;
|
|
198
|
+
recordSeedOnClick(pos);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
// Sessizce devam et
|
|
203
|
+
}
|
|
204
|
+
}, 50);
|
|
205
|
+
|
|
206
|
+
}, 3000);
|
|
207
|
+
|
|
208
|
+
// Ctrl+C handler
|
|
209
|
+
process.on('SIGINT', () => {
|
|
210
|
+
if (monitoringInterval) {
|
|
211
|
+
clearInterval(monitoringInterval);
|
|
212
|
+
}
|
|
213
|
+
console.log('\n\n⚠️ Yarıda kesildi. Şimdiye kadar toplanan data:\n');
|
|
214
|
+
Object.keys(seedMapping).forEach(cursorType => {
|
|
215
|
+
console.log(`${cursorType.padEnd(20)} → ${seedMapping[cursorType]}`);
|
|
216
|
+
});
|
|
217
|
+
console.log('\n');
|
|
218
|
+
process.exit(0);
|
|
219
|
+
});
|
package/index.js
CHANGED
|
@@ -1508,6 +1508,7 @@ class MacRecorder extends EventEmitter {
|
|
|
1508
1508
|
y: position.y - y,
|
|
1509
1509
|
cursorType: position.cursorType,
|
|
1510
1510
|
eventType: position.eventType,
|
|
1511
|
+
seed: position.seed,
|
|
1511
1512
|
displayId: display.id,
|
|
1512
1513
|
displayIndex: this.cachedDisplays.indexOf(display),
|
|
1513
1514
|
};
|
|
@@ -1523,6 +1524,7 @@ class MacRecorder extends EventEmitter {
|
|
|
1523
1524
|
y: position.y - parseInt(mainDisplay.y),
|
|
1524
1525
|
cursorType: position.cursorType,
|
|
1525
1526
|
eventType: position.eventType,
|
|
1527
|
+
seed: position.seed,
|
|
1526
1528
|
displayId: mainDisplay.id,
|
|
1527
1529
|
displayIndex: this.cachedDisplays.indexOf(mainDisplay),
|
|
1528
1530
|
outsideDisplay: true,
|