node-mac-recorder 2.22.32 → 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/index.js +15 -2
- package/package.json +4 -2
- package/scripts/cursor-type-live.js +32 -0
- package/scripts/cursor-types-15s-test.js +122 -0
- package/src/cursor_tracker.mm +381 -47
- package/src/electron_safe/cursor_tracker_electron.mm +36 -4
package/MultiWindowRecorder.js
CHANGED
|
@@ -223,6 +223,7 @@ class MultiWindowRecorder extends EventEmitter {
|
|
|
223
223
|
} : null,
|
|
224
224
|
recordingType: 'multi-window', // Multi-window recording type
|
|
225
225
|
startTimestamp: startTimestamp, // Use same timestamp as video
|
|
226
|
+
interval: 33,
|
|
226
227
|
// Pass window information for location detection
|
|
227
228
|
multiWindowBounds: windowBounds
|
|
228
229
|
};
|
package/index.js
CHANGED
|
@@ -869,7 +869,8 @@ class MacRecorder extends EventEmitter {
|
|
|
869
869
|
this.options.captureArea ? 'area' : 'display',
|
|
870
870
|
captureArea: this.options.captureArea,
|
|
871
871
|
windowId: this.options.windowId,
|
|
872
|
-
startTimestamp: syncTimestamp
|
|
872
|
+
startTimestamp: syncTimestamp,
|
|
873
|
+
interval: 33,
|
|
873
874
|
};
|
|
874
875
|
|
|
875
876
|
try {
|
|
@@ -1348,6 +1349,13 @@ class MacRecorder extends EventEmitter {
|
|
|
1348
1349
|
return true; // İlk event
|
|
1349
1350
|
}
|
|
1350
1351
|
|
|
1352
|
+
if (
|
|
1353
|
+
currentData.type === "drag" ||
|
|
1354
|
+
currentData.type === "rightdrag"
|
|
1355
|
+
) {
|
|
1356
|
+
return true;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1351
1359
|
const last = this.lastCapturedData;
|
|
1352
1360
|
|
|
1353
1361
|
// Event type değişmişse
|
|
@@ -1382,10 +1390,11 @@ class MacRecorder extends EventEmitter {
|
|
|
1382
1390
|
* @param {Object} options.captureArea - Capture area for area recording coordinate transformation
|
|
1383
1391
|
* @param {number} options.windowId - Window ID for window recording coordinate transformation
|
|
1384
1392
|
* @param {number} options.startTimestamp - Pre-defined start timestamp for synchronization (optional)
|
|
1393
|
+
* @param {number} options.interval - Örnekleme aralığı (ms), filepath ile çağrıda kullanılır; varsayılan 33ms (~30 Hz)
|
|
1385
1394
|
*/
|
|
1386
1395
|
async startCursorCapture(intervalOrFilepath = 100, options = {}) {
|
|
1387
1396
|
let filepath;
|
|
1388
|
-
let interval =
|
|
1397
|
+
let interval = 33;
|
|
1389
1398
|
|
|
1390
1399
|
// Parameter parsing: number = interval, string = filepath
|
|
1391
1400
|
if (typeof intervalOrFilepath === "number") {
|
|
@@ -1399,6 +1408,10 @@ class MacRecorder extends EventEmitter {
|
|
|
1399
1408
|
);
|
|
1400
1409
|
}
|
|
1401
1410
|
|
|
1411
|
+
if (typeof options.interval === "number") {
|
|
1412
|
+
interval = Math.max(10, options.interval);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1402
1415
|
if (this.cursorCaptureInterval) {
|
|
1403
1416
|
throw new Error("Cursor capture is already running");
|
|
1404
1417
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-mac-recorder",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.33",
|
|
4
4
|
"description": "Native macOS screen recording package for Node.js applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
"build:electron-safe": "node build-electron-safe.js",
|
|
44
44
|
"test:electron-safe": "node test-electron-safe.js",
|
|
45
45
|
"clean:electron-safe": "node-gyp clean && rm -rf build",
|
|
46
|
-
"canvas": "node make-canvas.js"
|
|
46
|
+
"canvas": "node make-canvas.js",
|
|
47
|
+
"cursor:live": "node scripts/cursor-type-live.js",
|
|
48
|
+
"test:cursor-types": "node scripts/cursor-types-15s-test.js"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
49
51
|
"node-addon-api": "^7.0.0"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const MacRecorder = require('../index.js');
|
|
4
|
+
|
|
5
|
+
const INTERVAL_MS = 33;
|
|
6
|
+
|
|
7
|
+
const recorder = new MacRecorder();
|
|
8
|
+
|
|
9
|
+
process.stderr.write(
|
|
10
|
+
`Canlı cursor tipi (${INTERVAL_MS}ms). Çıkmak: Ctrl+C\n`
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
function tick() {
|
|
14
|
+
try {
|
|
15
|
+
const p = recorder.getCursorPosition();
|
|
16
|
+
const type = p.cursorType != null ? String(p.cursorType) : '?';
|
|
17
|
+
const seed =
|
|
18
|
+
typeof p.seed === 'number' && p.seed > 0 ? ` seed:${p.seed}` : '';
|
|
19
|
+
process.stdout.write(`\r\x1b[KcursorType: ${type}${seed}`);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
process.stdout.write(`\r\x1b[K${e.message || e}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const id = setInterval(tick, INTERVAL_MS);
|
|
26
|
+
tick();
|
|
27
|
+
|
|
28
|
+
process.on('SIGINT', () => {
|
|
29
|
+
clearInterval(id);
|
|
30
|
+
process.stdout.write('\n');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const MacRecorder = require('../index.js');
|
|
5
|
+
|
|
6
|
+
const OUT_DIR = path.join(__dirname, '..', 'test-output');
|
|
7
|
+
const DURATION_SEC = 15;
|
|
8
|
+
|
|
9
|
+
async function sleep(ms) {
|
|
10
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function summarizeCursorTypes(cursorJsonPath) {
|
|
14
|
+
if (!cursorJsonPath || !fs.existsSync(cursorJsonPath)) {
|
|
15
|
+
return { counts: {}, ordered: [] };
|
|
16
|
+
}
|
|
17
|
+
const raw = fs.readFileSync(cursorJsonPath, 'utf8');
|
|
18
|
+
let events;
|
|
19
|
+
try {
|
|
20
|
+
events = JSON.parse(raw);
|
|
21
|
+
} catch {
|
|
22
|
+
return { counts: {}, ordered: [] };
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(events)) {
|
|
25
|
+
return { counts: {}, ordered: [] };
|
|
26
|
+
}
|
|
27
|
+
const counts = {};
|
|
28
|
+
const order = [];
|
|
29
|
+
for (const ev of events) {
|
|
30
|
+
const t = ev && ev.cursorType ? String(ev.cursorType) : '?';
|
|
31
|
+
if (counts[t] === undefined) {
|
|
32
|
+
counts[t] = 0;
|
|
33
|
+
order.push(t);
|
|
34
|
+
}
|
|
35
|
+
counts[t]++;
|
|
36
|
+
}
|
|
37
|
+
return { counts, ordered: order };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const stamp = Date.now();
|
|
44
|
+
const screenPath = path.join(OUT_DIR, `cursor-types-test-${stamp}.mov`);
|
|
45
|
+
|
|
46
|
+
const recorder = new MacRecorder();
|
|
47
|
+
|
|
48
|
+
const displays = await recorder.getDisplays();
|
|
49
|
+
const cameras = await recorder.getCameraDevices();
|
|
50
|
+
const audioDevices = await recorder.getAudioDevices();
|
|
51
|
+
|
|
52
|
+
if (!displays.length) {
|
|
53
|
+
process.stderr.write('No displays.\n');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const display = displays[0];
|
|
58
|
+
|
|
59
|
+
let cursorPathFromStart = null;
|
|
60
|
+
recorder.once('recordingStarted', (info) => {
|
|
61
|
+
cursorPathFromStart = info.cursorOutputPath || null;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const options = {
|
|
65
|
+
displayId: display.id,
|
|
66
|
+
captureCamera: cameras.length > 0,
|
|
67
|
+
cameraDeviceId: cameras.length > 0 ? cameras[0].id : undefined,
|
|
68
|
+
includeMicrophone: audioDevices.length > 0,
|
|
69
|
+
audioDeviceId: audioDevices.length > 0 ? audioDevices[0].id : undefined,
|
|
70
|
+
includeSystemAudio: true,
|
|
71
|
+
captureCursor: true,
|
|
72
|
+
frameRate: 60,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
process.stdout.write(
|
|
76
|
+
`Kayıt ${DURATION_SEC}s: ekran + sistem sesi + mik ${
|
|
77
|
+
options.includeMicrophone ? 'açık' : 'kapalı'
|
|
78
|
+
} + kamera ${options.captureCamera ? 'açık' : 'kapalı'} + cursor JSON.\n` +
|
|
79
|
+
`Kenarlarda resize, köşelerde çapraz, grab/grabbing ve crosshair deneyin.\n\n`
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await recorder.startRecording(screenPath, options);
|
|
83
|
+
|
|
84
|
+
const cursorPath = cursorPathFromStart;
|
|
85
|
+
|
|
86
|
+
for (let i = 1; i <= DURATION_SEC; i++) {
|
|
87
|
+
await sleep(1000);
|
|
88
|
+
process.stdout.write(`${i}s `);
|
|
89
|
+
}
|
|
90
|
+
process.stdout.write('\n');
|
|
91
|
+
|
|
92
|
+
await recorder.stopRecording();
|
|
93
|
+
|
|
94
|
+
process.stdout.write('\nDosyalar:\n');
|
|
95
|
+
process.stdout.write(` Ekran: ${screenPath}\n`);
|
|
96
|
+
if (options.captureCamera && cameras.length) {
|
|
97
|
+
process.stdout.write(
|
|
98
|
+
` Kamera: kayıt başladıktan sonra native temp_camera_* ile aynı oturum\n`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (options.includeMicrophone || options.includeSystemAudio) {
|
|
102
|
+
process.stdout.write(
|
|
103
|
+
` Ses: kayıt başladıktan sonra native temp_audio_* ile aynı oturum\n`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write(` Cursor JSON: ${cursorPath || 'bilinmiyor'}\n`);
|
|
107
|
+
|
|
108
|
+
if (cursorPath && fs.existsSync(cursorPath)) {
|
|
109
|
+
const { counts, ordered } = summarizeCursorTypes(cursorPath);
|
|
110
|
+
process.stdout.write('\nCursor JSON — cursorType özeti (ilk görülme sırası):\n');
|
|
111
|
+
for (const t of ordered) {
|
|
112
|
+
process.stdout.write(` ${t}: ${counts[t]}\n`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
recorder.removeAllListeners();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main().catch((e) => {
|
|
120
|
+
process.stderr.write(String(e && e.message ? e.message : e) + '\n');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
package/src/cursor_tracker.mm
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#import <dispatch/dispatch.h>
|
|
9
9
|
#import "logging.h"
|
|
10
10
|
#include <vector>
|
|
11
|
+
#include <cstring>
|
|
11
12
|
#include <math.h>
|
|
12
13
|
|
|
13
14
|
#ifndef kAXHitTestParameterizedAttribute
|
|
@@ -309,6 +310,180 @@ static NSCursor* CursorFromSelector(SEL selector) {
|
|
|
309
310
|
return func([NSCursor class], selector);
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
static BOOL CursorEqualsFactoryNamed(NSCursor *cursor, NSString *factoryMethodName) {
|
|
314
|
+
if (!cursor || !factoryMethodName || [factoryMethodName length] == 0) {
|
|
315
|
+
return NO;
|
|
316
|
+
}
|
|
317
|
+
SEL sel = NSSelectorFromString(factoryMethodName);
|
|
318
|
+
NSCursor *ref = CursorFromSelector(sel);
|
|
319
|
+
return ref != nil && cursor == ref;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static uint64_t CursorImagePixelHash(NSImage *image) {
|
|
323
|
+
if (!image) {
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
NSRect imageRect = NSMakeRect(0, 0, [image size].width, [image size].height);
|
|
327
|
+
CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:nil hints:nil];
|
|
328
|
+
if (!cgImage) {
|
|
329
|
+
for (NSImageRep *rep in [image representations]) {
|
|
330
|
+
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
|
|
331
|
+
cgImage = [(NSBitmapImageRep *)rep CGImage];
|
|
332
|
+
if (cgImage) {
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (!cgImage) {
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
size_t width = CGImageGetWidth(cgImage);
|
|
342
|
+
size_t height = CGImageGetHeight(cgImage);
|
|
343
|
+
if (width == 0 || height == 0) {
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
size_t bytesPerPixel = 4;
|
|
347
|
+
size_t bytesPerRow = width * bytesPerPixel;
|
|
348
|
+
size_t bufferSize = bytesPerRow * height;
|
|
349
|
+
if (bufferSize == 0) {
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
std::vector<unsigned char> buffer(bufferSize);
|
|
353
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
354
|
+
if (!colorSpace) {
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
|
|
358
|
+
CGContextRef context = CGBitmapContextCreate(buffer.data(),
|
|
359
|
+
width,
|
|
360
|
+
height,
|
|
361
|
+
8,
|
|
362
|
+
bytesPerRow,
|
|
363
|
+
colorSpace,
|
|
364
|
+
bitmapInfo);
|
|
365
|
+
CGColorSpaceRelease(colorSpace);
|
|
366
|
+
if (!context) {
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
|
370
|
+
CGContextRelease(context);
|
|
371
|
+
return FNV1AHash(buffer.data(), buffer.size());
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
static uint64_t CursorImagePixelHashNormalized32(NSImage *image) {
|
|
375
|
+
if (!image) {
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
const int dim = 32;
|
|
379
|
+
size_t bytesPerPixel = 4;
|
|
380
|
+
size_t bytesPerRow = (size_t)dim * bytesPerPixel;
|
|
381
|
+
std::vector<unsigned char> buffer(bytesPerRow * (size_t)dim);
|
|
382
|
+
memset(buffer.data(), 0, buffer.size());
|
|
383
|
+
|
|
384
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
385
|
+
if (!colorSpace) {
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
CGContextRef ctx = CGBitmapContextCreate(buffer.data(),
|
|
389
|
+
(size_t)dim,
|
|
390
|
+
(size_t)dim,
|
|
391
|
+
8,
|
|
392
|
+
bytesPerRow,
|
|
393
|
+
colorSpace,
|
|
394
|
+
kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
|
|
395
|
+
CGColorSpaceRelease(colorSpace);
|
|
396
|
+
if (!ctx) {
|
|
397
|
+
return 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
CGContextClearRect(ctx, CGRectMake(0, 0, dim, dim));
|
|
401
|
+
NSGraphicsContext *nsgc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
|
|
402
|
+
[NSGraphicsContext saveGraphicsState];
|
|
403
|
+
[NSGraphicsContext setCurrentContext:nsgc];
|
|
404
|
+
|
|
405
|
+
NSSize sz = [image size];
|
|
406
|
+
if (sz.width <= 0 || sz.height <= 0) {
|
|
407
|
+
[NSGraphicsContext restoreGraphicsState];
|
|
408
|
+
CGContextRelease(ctx);
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
CGFloat scale = MIN((CGFloat)dim / sz.width, (CGFloat)dim / sz.height);
|
|
412
|
+
CGFloat rw = sz.width * scale;
|
|
413
|
+
CGFloat rh = sz.height * scale;
|
|
414
|
+
CGFloat ox = ((CGFloat)dim - rw) / 2.0;
|
|
415
|
+
CGFloat oy = ((CGFloat)dim - rh) / 2.0;
|
|
416
|
+
[image drawInRect:NSMakeRect(ox, oy, rw, rh)
|
|
417
|
+
fromRect:NSZeroRect
|
|
418
|
+
operation:NSCompositingOperationSourceOver
|
|
419
|
+
fraction:1.0
|
|
420
|
+
respectFlipped:YES
|
|
421
|
+
hints:nil];
|
|
422
|
+
|
|
423
|
+
[NSGraphicsContext restoreGraphicsState];
|
|
424
|
+
CGContextRelease(ctx);
|
|
425
|
+
return FNV1AHash(buffer.data(), buffer.size());
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
static NSString* DiagonalResizeTypeByVisualMatch(NSCursor *cursor) {
|
|
429
|
+
if (!cursor) {
|
|
430
|
+
return nil;
|
|
431
|
+
}
|
|
432
|
+
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthWestSouthEastCursor")) {
|
|
433
|
+
return @"nwse-resize";
|
|
434
|
+
}
|
|
435
|
+
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthEastSouthWestCursor")) {
|
|
436
|
+
return @"nesw-resize";
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
NSImage *curImg = [cursor image];
|
|
440
|
+
if (!curImg) {
|
|
441
|
+
return nil;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
NSData *curTIFF = [curImg TIFFRepresentation];
|
|
445
|
+
uint64_t curRaw = CursorImagePixelHash(curImg);
|
|
446
|
+
uint64_t curNorm = CursorImagePixelHashNormalized32(curImg);
|
|
447
|
+
|
|
448
|
+
struct DiagonalFactoryEntry {
|
|
449
|
+
const char *selectorName;
|
|
450
|
+
const char *type;
|
|
451
|
+
};
|
|
452
|
+
static const DiagonalFactoryEntry kDiagonalFactories[] = {
|
|
453
|
+
{ "resizeNorthWestSouthEastCursor", "nwse-resize" },
|
|
454
|
+
{ "resizeNorthEastSouthWestCursor", "nesw-resize" },
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
for (size_t i = 0; i < sizeof(kDiagonalFactories) / sizeof(kDiagonalFactories[0]); i++) {
|
|
458
|
+
NSString *selStr = [NSString stringWithUTF8String:kDiagonalFactories[i].selectorName];
|
|
459
|
+
SEL sel = NSSelectorFromString(selStr);
|
|
460
|
+
if (![NSCursor respondsToSelector:sel]) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
NSCursor *ref = CursorFromSelector(sel);
|
|
464
|
+
if (!ref) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
NSImage *refImg = [ref image];
|
|
468
|
+
if (!refImg) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
NSData *refTIFF = [refImg TIFFRepresentation];
|
|
472
|
+
if (curTIFF && refTIFF && [curTIFF isEqualToData:refTIFF]) {
|
|
473
|
+
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
474
|
+
}
|
|
475
|
+
uint64_t refRaw = CursorImagePixelHash(refImg);
|
|
476
|
+
if (curRaw != 0 && curRaw == refRaw) {
|
|
477
|
+
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
478
|
+
}
|
|
479
|
+
uint64_t refNorm = CursorImagePixelHashNormalized32(refImg);
|
|
480
|
+
if (curNorm != 0 && curNorm == refNorm) {
|
|
481
|
+
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return nil;
|
|
485
|
+
}
|
|
486
|
+
|
|
312
487
|
static void AddStandardCursorFingerprint(NSCursor *cursor, NSString *cursorType) {
|
|
313
488
|
if (!cursor || !cursorType) {
|
|
314
489
|
return;
|
|
@@ -359,7 +534,7 @@ static void InitializeCursorFingerprintMap(void) {
|
|
|
359
534
|
AddCursorIfAvailable(@selector(dragCopyCursor), @"copy");
|
|
360
535
|
AddCursorIfAvailable(@selector(dragLinkCursor), @"alias");
|
|
361
536
|
AddCursorIfAvailable(@selector(resizeLeftRightCursor), @"col-resize");
|
|
362
|
-
AddCursorIfAvailable(@selector(resizeUpDownCursor), @"
|
|
537
|
+
AddCursorIfAvailable(@selector(resizeUpDownCursor), @"ns-resize");
|
|
363
538
|
AddCursorIfAvailableByName(@"resizeLeftCursor", @"col-resize");
|
|
364
539
|
AddCursorIfAvailableByName(@"resizeRightCursor", @"col-resize");
|
|
365
540
|
AddCursorIfAvailableByName(@"resizeUpCursor", @"ns-resize");
|
|
@@ -502,21 +677,26 @@ static BOOL ShouldEmitCursorEvent(CGPoint location, NSString *cursorType, NSStri
|
|
|
502
677
|
BOOL moved = fabs(location.x - g_lastCursorLocation.x) >= movementThreshold ||
|
|
503
678
|
fabs(location.y - g_lastCursorLocation.y) >= movementThreshold;
|
|
504
679
|
BOOL eventChanged = !StringsEqual(eventType, g_lastCursorEventType);
|
|
505
|
-
BOOL
|
|
680
|
+
BOOL isDragEvent = StringsEqual(eventType, @"drag") || StringsEqual(eventType, @"rightdrag");
|
|
681
|
+
BOOL isMoveOnly = StringsEqual(eventType, @"move");
|
|
506
682
|
BOOL isClickEvent = StringsEqual(eventType, @"mousedown") ||
|
|
507
683
|
StringsEqual(eventType, @"mouseup") ||
|
|
508
684
|
StringsEqual(eventType, @"rightmousedown") ||
|
|
509
685
|
StringsEqual(eventType, @"rightmouseup");
|
|
510
686
|
|
|
511
|
-
if (
|
|
512
|
-
return
|
|
687
|
+
if (isDragEvent) {
|
|
688
|
+
return YES;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (isMoveOnly) {
|
|
692
|
+
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
693
|
+
return moved || cursorChanged;
|
|
513
694
|
}
|
|
514
695
|
|
|
515
696
|
if (isClickEvent) {
|
|
516
697
|
return eventChanged || moved;
|
|
517
698
|
}
|
|
518
699
|
|
|
519
|
-
// Fallback: only emit when something actually changed
|
|
520
700
|
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
521
701
|
return moved || cursorChanged || eventChanged;
|
|
522
702
|
}
|
|
@@ -670,7 +850,10 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
670
850
|
CFRelease(positionValue);
|
|
671
851
|
CFRelease(sizeValue);
|
|
672
852
|
|
|
673
|
-
|
|
853
|
+
// Köşe: köşe noktasına uzaklık (kutu sınırında col ↔ diagonal sıçramasını azaltır).
|
|
854
|
+
const CGFloat cornerRadius = 9.0;
|
|
855
|
+
const CGFloat edgeInset = 12.0;
|
|
856
|
+
|
|
674
857
|
CGFloat x = cursorPos.x - windowOrigin.x;
|
|
675
858
|
CGFloat y = cursorPos.y - windowOrigin.y;
|
|
676
859
|
CGFloat w = windowSize.width;
|
|
@@ -680,17 +863,44 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
680
863
|
return nil;
|
|
681
864
|
}
|
|
682
865
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
866
|
+
if (w < cornerRadius * 2.1 || h < cornerRadius * 2.1) {
|
|
867
|
+
CGFloat edge = edgeInset;
|
|
868
|
+
BOOL nearLeft = (x >= 0 && x <= edge);
|
|
869
|
+
BOOL nearRight = (x >= w - edge && x <= w);
|
|
870
|
+
BOOL nearTop = (y >= 0 && y <= edge);
|
|
871
|
+
BOOL nearBottom = (y >= h - edge && y <= h);
|
|
872
|
+
if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
|
|
873
|
+
return @"nwse-resize";
|
|
874
|
+
}
|
|
875
|
+
if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
|
|
876
|
+
return @"nesw-resize";
|
|
877
|
+
}
|
|
878
|
+
if (nearLeft || nearRight) {
|
|
879
|
+
return @"col-resize";
|
|
880
|
+
}
|
|
881
|
+
if (nearTop || nearBottom) {
|
|
882
|
+
return @"ns-resize";
|
|
883
|
+
}
|
|
884
|
+
return nil;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
CGFloat dBottomLeft = hypot(x, y);
|
|
888
|
+
CGFloat dTopRight = hypot(w - x, h - y);
|
|
889
|
+
CGFloat dBottomRight = hypot(w - x, y);
|
|
890
|
+
CGFloat dTopLeft = hypot(x, h - y);
|
|
687
891
|
|
|
688
|
-
if (
|
|
892
|
+
if (dBottomLeft <= cornerRadius || dTopRight <= cornerRadius) {
|
|
689
893
|
return @"nwse-resize";
|
|
690
894
|
}
|
|
691
|
-
if (
|
|
895
|
+
if (dBottomRight <= cornerRadius || dTopLeft <= cornerRadius) {
|
|
692
896
|
return @"nesw-resize";
|
|
693
897
|
}
|
|
898
|
+
|
|
899
|
+
BOOL nearLeft = (x >= 0 && x <= edgeInset);
|
|
900
|
+
BOOL nearRight = (x >= w - edgeInset && x <= w);
|
|
901
|
+
BOOL nearTop = (y >= 0 && y <= edgeInset);
|
|
902
|
+
BOOL nearBottom = (y >= h - edgeInset && y <= h);
|
|
903
|
+
|
|
694
904
|
if (nearLeft || nearRight) {
|
|
695
905
|
return @"col-resize";
|
|
696
906
|
}
|
|
@@ -701,6 +911,42 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
701
911
|
return nil;
|
|
702
912
|
}
|
|
703
913
|
|
|
914
|
+
static NSString* ResizeTypeFromWindowUnderCursor(CGPoint cursorPos) {
|
|
915
|
+
@autoreleasepool {
|
|
916
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
917
|
+
if (!systemWide) {
|
|
918
|
+
return nil;
|
|
919
|
+
}
|
|
920
|
+
AXUIElementRef hit = NULL;
|
|
921
|
+
AXError err = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &hit);
|
|
922
|
+
CFRelease(systemWide);
|
|
923
|
+
if (err != kAXErrorSuccess || !hit) {
|
|
924
|
+
if (hit) {
|
|
925
|
+
CFRelease(hit);
|
|
926
|
+
}
|
|
927
|
+
return nil;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
AXUIElementRef node = hit;
|
|
931
|
+
for (int depth = 0; depth < 24 && node; depth++) {
|
|
932
|
+
NSString *role = CopyAttributeString(node, kAXRoleAttribute);
|
|
933
|
+
if ([role isEqualToString:@"AXWindow"]) {
|
|
934
|
+
NSString *borderType = CursorTypeForWindowBorder(node, cursorPos);
|
|
935
|
+
CFRelease(node);
|
|
936
|
+
return borderType;
|
|
937
|
+
}
|
|
938
|
+
AXUIElementRef parent = NULL;
|
|
939
|
+
AXError perr = AXUIElementCopyAttributeValue(node, kAXParentAttribute, (CFTypeRef *)&parent);
|
|
940
|
+
CFRelease(node);
|
|
941
|
+
node = NULL;
|
|
942
|
+
if (perr == kAXErrorSuccess && parent) {
|
|
943
|
+
node = parent;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return nil;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
704
950
|
static NSString* CursorTypeFromAccessibilityElement(AXUIElementRef element, CGPoint cursorPos) {
|
|
705
951
|
if (!element) {
|
|
706
952
|
return nil;
|
|
@@ -937,6 +1183,14 @@ static NSString* cursorTypeFromCursorName(NSString *value) {
|
|
|
937
1183
|
return @"zoom-in";
|
|
938
1184
|
}
|
|
939
1185
|
|
|
1186
|
+
// CGS / tema kısa diagonal tokenları (nwse / nesw)
|
|
1187
|
+
if ([normalized containsString:@"nesw"]) {
|
|
1188
|
+
return @"nesw-resize";
|
|
1189
|
+
}
|
|
1190
|
+
if ([normalized containsString:@"nwse"]) {
|
|
1191
|
+
return @"nwse-resize";
|
|
1192
|
+
}
|
|
1193
|
+
|
|
940
1194
|
// All-scroll pattern (move in all directions)
|
|
941
1195
|
if ([normalized containsString:@"all-scroll"] ||
|
|
942
1196
|
[normalized containsString:@"allscroll"] ||
|
|
@@ -1383,6 +1637,13 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1383
1637
|
CGFloat relativeX = width > 0 ? hotspot.x / width : 0;
|
|
1384
1638
|
CGFloat relativeY = height > 0 ? hotspot.y / height : 0;
|
|
1385
1639
|
|
|
1640
|
+
if (cursor) {
|
|
1641
|
+
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1642
|
+
if (diag) {
|
|
1643
|
+
return diag;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1386
1647
|
// Tolerance for floating point comparison
|
|
1387
1648
|
CGFloat tolerance = 0.05;
|
|
1388
1649
|
CGFloat tightTolerance = 0.02; // For precise hotspot matching
|
|
@@ -1422,29 +1683,35 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1422
1683
|
// Distinguished by pointer equality
|
|
1423
1684
|
if (approx(width, 32) && approx(height, 32) && approx(relativeY, 0.531)) {
|
|
1424
1685
|
if (cursor) {
|
|
1425
|
-
if (
|
|
1686
|
+
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] &&
|
|
1687
|
+
cursor == [NSCursor closedHandCursor]) {
|
|
1426
1688
|
return @"grabbing";
|
|
1427
1689
|
}
|
|
1428
|
-
if (
|
|
1690
|
+
if ([NSCursor respondsToSelector:@selector(openHandCursor)] &&
|
|
1691
|
+
cursor == [NSCursor openHandCursor]) {
|
|
1429
1692
|
return @"grab";
|
|
1430
1693
|
}
|
|
1431
1694
|
}
|
|
1432
1695
|
return @"grab"; // Default to grab if can't distinguish
|
|
1433
1696
|
}
|
|
1434
1697
|
|
|
1435
|
-
// 24x24
|
|
1436
|
-
// Distinguished by precise hotspot position
|
|
1698
|
+
// 24x24: köşe diagonal resize ile crosshair/move aynı boyutta — önce diagonal fabrika
|
|
1437
1699
|
if (approx(width, 24) && approx(height, 24)) {
|
|
1700
|
+
if (cursor) {
|
|
1701
|
+
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1702
|
+
if (diag) {
|
|
1703
|
+
return diag;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1438
1706
|
// crosshair: hotspot rel=(0.458, 0.458)
|
|
1439
1707
|
if (approxTight(relativeX, 0.458) && approxTight(relativeY, 0.458)) {
|
|
1440
1708
|
return @"crosshair";
|
|
1441
1709
|
}
|
|
1442
1710
|
// move/all-scroll: hotspot rel=(0.5, 0.5)
|
|
1443
1711
|
if (approxTight(relativeX, 0.5) && approxTight(relativeY, 0.5)) {
|
|
1444
|
-
return @"move";
|
|
1712
|
+
return @"move";
|
|
1445
1713
|
}
|
|
1446
|
-
|
|
1447
|
-
return @"crosshair";
|
|
1714
|
+
return nil;
|
|
1448
1715
|
}
|
|
1449
1716
|
|
|
1450
1717
|
// help/cell: 18x18, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
@@ -1505,7 +1772,29 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1505
1772
|
|
|
1506
1773
|
// ne-resize/nw-resize/se-resize/sw-resize/nesw-resize/nwse-resize: 22x22, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
1507
1774
|
if (approx(width, 22) && approx(height, 22)) {
|
|
1508
|
-
|
|
1775
|
+
if (cursor) {
|
|
1776
|
+
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1777
|
+
if (diag) {
|
|
1778
|
+
return diag;
|
|
1779
|
+
}
|
|
1780
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
|
|
1781
|
+
cursor == [NSCursor resizeUpCursor]) {
|
|
1782
|
+
return @"n-resize";
|
|
1783
|
+
}
|
|
1784
|
+
if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
|
|
1785
|
+
cursor == [NSCursor resizeDownCursor]) {
|
|
1786
|
+
return @"s-resize";
|
|
1787
|
+
}
|
|
1788
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
|
|
1789
|
+
cursor == [NSCursor resizeLeftCursor]) {
|
|
1790
|
+
return @"w-resize";
|
|
1791
|
+
}
|
|
1792
|
+
if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
|
|
1793
|
+
cursor == [NSCursor resizeRightCursor]) {
|
|
1794
|
+
return @"e-resize";
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return nil;
|
|
1509
1798
|
}
|
|
1510
1799
|
|
|
1511
1800
|
// zoom-in/zoom-out: 28x26, ratio=1.077, hotspot rel=(0.428, 0.423)
|
|
@@ -1527,10 +1816,12 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1527
1816
|
if (approxTight(relativeX, 0.161) && approxTight(relativeY, 0.1)) {
|
|
1528
1817
|
return @"default";
|
|
1529
1818
|
}
|
|
1530
|
-
//
|
|
1819
|
+
// Ortak 28x40 ok/beachball hotspot bandı: spinner ile karışmasın diye önce ok
|
|
1531
1820
|
if (approxTight(relativeX, 0.179) && approxTight(relativeY, 0.125)) {
|
|
1532
|
-
// Try pointer equality for standard cursors
|
|
1533
1821
|
if (cursor) {
|
|
1822
|
+
if (cursor == [NSCursor arrowCursor]) {
|
|
1823
|
+
return @"default";
|
|
1824
|
+
}
|
|
1534
1825
|
if (cursor == [NSCursor contextualMenuCursor]) {
|
|
1535
1826
|
return @"context-menu";
|
|
1536
1827
|
}
|
|
@@ -1541,10 +1832,7 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1541
1832
|
return @"not-allowed";
|
|
1542
1833
|
}
|
|
1543
1834
|
}
|
|
1544
|
-
|
|
1545
|
-
// Return "progress" as default for this hotspot pattern (better than "default")
|
|
1546
|
-
// Let cursor name detection in caller distinguish between progress/wait
|
|
1547
|
-
return @"progress";
|
|
1835
|
+
return @"default";
|
|
1548
1836
|
}
|
|
1549
1837
|
return @"default";
|
|
1550
1838
|
}
|
|
@@ -1571,13 +1859,16 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
|
1571
1859
|
if (cursor == [NSCursor pointingHandCursor]) {
|
|
1572
1860
|
return @"pointer";
|
|
1573
1861
|
}
|
|
1574
|
-
if (
|
|
1862
|
+
if ([NSCursor respondsToSelector:@selector(crosshairCursor)] &&
|
|
1863
|
+
cursor == [NSCursor crosshairCursor]) {
|
|
1575
1864
|
return @"crosshair";
|
|
1576
1865
|
}
|
|
1577
|
-
if (
|
|
1866
|
+
if ([NSCursor respondsToSelector:@selector(openHandCursor)] &&
|
|
1867
|
+
cursor == [NSCursor openHandCursor]) {
|
|
1578
1868
|
return @"grab";
|
|
1579
1869
|
}
|
|
1580
|
-
if (
|
|
1870
|
+
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] &&
|
|
1871
|
+
cursor == [NSCursor closedHandCursor]) {
|
|
1581
1872
|
return @"grabbing";
|
|
1582
1873
|
}
|
|
1583
1874
|
if (cursor == [NSCursor operationNotAllowedCursor]) {
|
|
@@ -1593,14 +1884,42 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
|
1593
1884
|
return @"context-menu";
|
|
1594
1885
|
}
|
|
1595
1886
|
|
|
1596
|
-
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
1887
|
+
NSString *diagonalVisual = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1888
|
+
if (diagonalVisual) {
|
|
1889
|
+
return diagonalVisual;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
|
|
1893
|
+
cursor == [NSCursor resizeLeftCursor]) {
|
|
1894
|
+
return @"col-resize";
|
|
1895
|
+
}
|
|
1896
|
+
if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
|
|
1897
|
+
cursor == [NSCursor resizeRightCursor]) {
|
|
1898
|
+
return @"col-resize";
|
|
1899
|
+
}
|
|
1900
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
|
|
1901
|
+
cursor == [NSCursor resizeUpCursor]) {
|
|
1902
|
+
return @"n-resize";
|
|
1903
|
+
}
|
|
1904
|
+
if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
|
|
1905
|
+
cursor == [NSCursor resizeDownCursor]) {
|
|
1906
|
+
return @"s-resize";
|
|
1907
|
+
}
|
|
1908
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)] &&
|
|
1909
|
+
cursor == [NSCursor resizeLeftRightCursor]) {
|
|
1910
|
+
return @"col-resize";
|
|
1911
|
+
}
|
|
1912
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)] &&
|
|
1913
|
+
cursor == [NSCursor resizeUpDownCursor]) {
|
|
1914
|
+
return @"ns-resize";
|
|
1915
|
+
}
|
|
1916
|
+
if (@available(macOS 15.0, *)) {
|
|
1917
|
+
if ([NSCursor respondsToSelector:@selector(columnResizeCursor)] &&
|
|
1918
|
+
cursor == [NSCursor columnResizeCursor]) {
|
|
1599
1919
|
return @"col-resize";
|
|
1600
1920
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if (cursor == [NSCursor resizeUpDownCursor]) {
|
|
1921
|
+
if ([NSCursor respondsToSelector:@selector(rowResizeCursor)] &&
|
|
1922
|
+
cursor == [NSCursor rowResizeCursor]) {
|
|
1604
1923
|
return @"row-resize";
|
|
1605
1924
|
}
|
|
1606
1925
|
}
|
|
@@ -1672,12 +1991,6 @@ static NSString* detectSystemCursorType(void) {
|
|
|
1672
1991
|
}
|
|
1673
1992
|
|
|
1674
1993
|
int cursorSeed = SafeCGSCurrentCursorSeed();
|
|
1675
|
-
if (cursorSeed > 0) {
|
|
1676
|
-
NSString *seedType = cursorTypeFromSeed(cursorSeed);
|
|
1677
|
-
if (seedType) {
|
|
1678
|
-
return seedType;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
1994
|
|
|
1682
1995
|
void (^fetchCursorBlock)(void) = ^{
|
|
1683
1996
|
NSCursor *currentCursor = nil;
|
|
@@ -1781,14 +2094,23 @@ static NSString* detectSystemCursorType(void) {
|
|
|
1781
2094
|
dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
|
|
1782
2095
|
}
|
|
1783
2096
|
|
|
1784
|
-
if (cursorType && ![cursorType isEqualToString:@"default"]
|
|
1785
|
-
|
|
2097
|
+
if (cursorType && ![cursorType isEqualToString:@"default"]) {
|
|
2098
|
+
if (cursorSeed > 0) {
|
|
2099
|
+
addCursorToSeedMap(cursorType, cursorSeed);
|
|
2100
|
+
}
|
|
2101
|
+
return cursorType;
|
|
1786
2102
|
}
|
|
1787
2103
|
|
|
1788
|
-
|
|
2104
|
+
if (cursorSeed > 0) {
|
|
2105
|
+
NSString *seedType = cursorTypeFromSeed(cursorSeed);
|
|
2106
|
+
if (seedType) {
|
|
2107
|
+
return seedType;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
return cursorType ?: @"default";
|
|
1789
2112
|
}
|
|
1790
2113
|
|
|
1791
|
-
// Desktop'ta SVG karşılığı olmayan cursor tiplerini desteklenen tiplere normalize et
|
|
1792
2114
|
static NSString* normalizeCursorTypeForDesktop(NSString *cursorType) {
|
|
1793
2115
|
if (!cursorType || [cursorType length] == 0) {
|
|
1794
2116
|
return @"default";
|
|
@@ -1886,11 +2208,23 @@ NSString* getCursorType() {
|
|
|
1886
2208
|
int currentSeed = SafeCGSCurrentCursorSeed();
|
|
1887
2209
|
g_lastCursorSeed = currentSeed; // Save for getCursorPosition()
|
|
1888
2210
|
|
|
1889
|
-
//
|
|
1890
|
-
// DO NOT use accessibility detection as it's unreliable and causes false positives
|
|
2211
|
+
// Önce NSCursor/CGS; köşe diagonal için sistem imleci güvenilir olmayabiliyor — AX ile pencere çerçevesi düzeltmesi
|
|
1891
2212
|
NSString *systemCursorType = detectSystemCursorType();
|
|
1892
2213
|
NSString *rawType = systemCursorType && [systemCursorType length] > 0 ? systemCursorType : @"default";
|
|
1893
2214
|
|
|
2215
|
+
__block NSString *chromeResize = nil;
|
|
2216
|
+
if ([NSThread isMainThread]) {
|
|
2217
|
+
chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
|
|
2218
|
+
} else {
|
|
2219
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
2220
|
+
chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
if (chromeResize) {
|
|
2225
|
+
rawType = chromeResize;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
1894
2228
|
// Desktop SVG'lerine uyumlu tipe normalize et
|
|
1895
2229
|
NSString *finalType = normalizeCursorTypeForDesktop(rawType);
|
|
1896
2230
|
|
|
@@ -3,6 +3,22 @@
|
|
|
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
|
+
}
|
|
21
|
+
|
|
6
22
|
// Thread-safe cursor tracking for Electron
|
|
7
23
|
static dispatch_queue_t g_cursorQueue = nil;
|
|
8
24
|
|
|
@@ -23,18 +39,34 @@ static NSString* MapCursorToType(NSCursor *cursor) {
|
|
|
23
39
|
if (cursor == [NSCursor pointingHandCursor]) return @"pointer";
|
|
24
40
|
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
25
41
|
if (cursor == [NSCursor resizeLeftRightCursor] ||
|
|
26
|
-
[NSCursor
|
|
27
|
-
[NSCursor
|
|
42
|
+
([NSCursor respondsToSelector:@selector(resizeLeftCursor)] && cursor == [NSCursor resizeLeftCursor]) ||
|
|
43
|
+
([NSCursor respondsToSelector:@selector(resizeRightCursor)] && cursor == [NSCursor resizeRightCursor])) {
|
|
28
44
|
return @"col-resize";
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
47
|
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
32
48
|
if (cursor == [NSCursor resizeUpDownCursor] ||
|
|
33
|
-
[NSCursor
|
|
34
|
-
[NSCursor
|
|
49
|
+
([NSCursor respondsToSelector:@selector(resizeUpCursor)] && cursor == [NSCursor resizeUpCursor]) ||
|
|
50
|
+
([NSCursor respondsToSelector:@selector(resizeDownCursor)] && cursor == [NSCursor resizeDownCursor])) {
|
|
35
51
|
return @"ns-resize";
|
|
36
52
|
}
|
|
37
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
|
+
}
|
|
38
70
|
if ([NSCursor respondsToSelector:@selector(openHandCursor)] && cursor == [NSCursor openHandCursor]) return @"grab";
|
|
39
71
|
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] && cursor == [NSCursor closedHandCursor]) return @"grabbing";
|
|
40
72
|
if ([NSCursor respondsToSelector:@selector(crosshairCursor)] && cursor == [NSCursor crosshairCursor]) return @"crosshair";
|