node-mac-recorder 1.2.2 → 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.
@@ -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/cursor-data.json CHANGED
@@ -1,93 +1,359 @@
1
1
  [
2
2
  {
3
- "x": 1119,
4
- "y": 1090,
5
- "timestamp": 106,
3
+ "x": 1947,
4
+ "y": 780,
5
+ "timestamp": 85,
6
+ "cursorType": "pointer",
7
+ "type": "move"
8
+ },
9
+ {
10
+ "x": 1950,
11
+ "y": 780,
12
+ "timestamp": 1476,
13
+ "cursorType": "pointer",
14
+ "type": "move"
15
+ },
16
+ {
17
+ "x": 1954,
18
+ "y": 778,
19
+ "timestamp": 1500,
20
+ "cursorType": "pointer",
21
+ "type": "move"
22
+ },
23
+ {
24
+ "x": 1958,
25
+ "y": 777,
26
+ "timestamp": 1518,
27
+ "cursorType": "pointer",
28
+ "type": "move"
29
+ },
30
+ {
31
+ "x": 1963,
32
+ "y": 775,
33
+ "timestamp": 1541,
34
+ "cursorType": "pointer",
35
+ "type": "move"
36
+ },
37
+ {
38
+ "x": 1967,
39
+ "y": 775,
40
+ "timestamp": 1595,
6
41
  "cursorType": "default",
7
42
  "type": "move"
8
43
  },
9
44
  {
10
- "x": 1119,
11
- "y": 1080,
12
- "timestamp": 407,
45
+ "x": 1971,
46
+ "y": 775,
47
+ "timestamp": 1596,
13
48
  "cursorType": "default",
14
49
  "type": "move"
15
50
  },
16
51
  {
17
- "x": 1119,
18
- "y": 1066,
19
- "timestamp": 423,
52
+ "x": 1973,
53
+ "y": 775,
54
+ "timestamp": 1617,
20
55
  "cursorType": "default",
21
56
  "type": "move"
22
57
  },
23
58
  {
24
- "x": 1119,
25
- "y": 1042,
26
- "timestamp": 446,
59
+ "x": 1975,
60
+ "y": 775,
61
+ "timestamp": 1659,
27
62
  "cursorType": "default",
28
63
  "type": "move"
29
64
  },
30
65
  {
31
- "x": 1119,
32
- "y": 968,
33
- "timestamp": 466,
66
+ "x": 1977,
67
+ "y": 775,
68
+ "timestamp": 1679,
69
+ "cursorType": "pointer",
70
+ "type": "move"
71
+ },
72
+ {
73
+ "x": 1979,
74
+ "y": 775,
75
+ "timestamp": 1744,
76
+ "cursorType": "pointer",
77
+ "type": "move"
78
+ },
79
+ {
80
+ "x": 1979,
81
+ "y": 777,
82
+ "timestamp": 1807,
83
+ "cursorType": "pointer",
84
+ "type": "move"
85
+ },
86
+ {
87
+ "x": 1973,
88
+ "y": 782,
89
+ "timestamp": 1828,
34
90
  "cursorType": "default",
35
91
  "type": "move"
36
92
  },
37
93
  {
38
- "x": 1129,
39
- "y": 906,
40
- "timestamp": 487,
94
+ "x": 1968,
95
+ "y": 787,
96
+ "timestamp": 1850,
41
97
  "cursorType": "default",
42
98
  "type": "move"
43
99
  },
44
100
  {
45
- "x": 1153,
46
- "y": 820,
47
- "timestamp": 507,
101
+ "x": 1957,
102
+ "y": 793,
103
+ "timestamp": 1868,
104
+ "cursorType": "pointer",
105
+ "type": "move"
106
+ },
107
+ {
108
+ "x": 1948,
109
+ "y": 799,
110
+ "timestamp": 1891,
111
+ "cursorType": "pointer",
112
+ "type": "move"
113
+ },
114
+ {
115
+ "x": 1943,
116
+ "y": 802,
117
+ "timestamp": 1911,
48
118
  "cursorType": "default",
49
119
  "type": "move"
50
120
  },
51
121
  {
52
- "x": 1168,
53
- "y": 782,
54
- "timestamp": 528,
122
+ "x": 1937,
123
+ "y": 804,
124
+ "timestamp": 1933,
125
+ "cursorType": "default",
126
+ "type": "move"
127
+ },
128
+ {
129
+ "x": 1938,
130
+ "y": 800,
131
+ "timestamp": 2309,
132
+ "cursorType": "default",
133
+ "type": "move"
134
+ },
135
+ {
136
+ "x": 1939,
137
+ "y": 799,
138
+ "timestamp": 2329,
139
+ "cursorType": "pointer",
140
+ "type": "move"
141
+ },
142
+ {
143
+ "x": 1940,
144
+ "y": 797,
145
+ "timestamp": 2435,
146
+ "cursorType": "pointer",
147
+ "type": "move"
148
+ },
149
+ {
150
+ "x": 1941,
151
+ "y": 794,
152
+ "timestamp": 2477,
153
+ "cursorType": "pointer",
154
+ "type": "move"
155
+ },
156
+ {
157
+ "x": 1941,
158
+ "y": 792,
159
+ "timestamp": 2500,
160
+ "cursorType": "pointer",
161
+ "type": "move"
162
+ },
163
+ {
164
+ "x": 1942,
165
+ "y": 788,
166
+ "timestamp": 2518,
167
+ "cursorType": "pointer",
168
+ "type": "move"
169
+ },
170
+ {
171
+ "x": 1943,
172
+ "y": 786,
173
+ "timestamp": 2560,
174
+ "cursorType": "pointer",
175
+ "type": "move"
176
+ },
177
+ {
178
+ "x": 1945,
179
+ "y": 784,
180
+ "timestamp": 2707,
181
+ "cursorType": "pointer",
182
+ "type": "move"
183
+ },
184
+ {
185
+ "x": 1945,
186
+ "y": 796,
187
+ "timestamp": 3544,
188
+ "cursorType": "pointer",
189
+ "type": "move"
190
+ },
191
+ {
192
+ "x": 1945,
193
+ "y": 817,
194
+ "timestamp": 3568,
55
195
  "cursorType": "default",
56
196
  "type": "move"
57
197
  },
58
198
  {
59
- "x": 1176,
60
- "y": 764,
61
- "timestamp": 549,
199
+ "x": 1951,
200
+ "y": 863,
201
+ "timestamp": 3589,
62
202
  "cursorType": "default",
63
203
  "type": "move"
64
204
  },
65
205
  {
66
- "x": 1176,
67
- "y": 764,
68
- "timestamp": 2578,
206
+ "x": 1956,
207
+ "y": 925,
208
+ "timestamp": 3609,
69
209
  "cursorType": "default",
70
- "type": "mousedown"
210
+ "type": "move"
71
211
  },
72
212
  {
73
- "x": 1176,
74
- "y": 764,
75
- "timestamp": 2599,
213
+ "x": 1966,
214
+ "y": 983,
215
+ "timestamp": 3630,
76
216
  "cursorType": "default",
77
217
  "type": "move"
78
218
  },
79
219
  {
80
- "x": 1176,
81
- "y": 764,
82
- "timestamp": 2724,
220
+ "x": 1970,
221
+ "y": 1006,
222
+ "timestamp": 3650,
223
+ "cursorType": "text",
224
+ "type": "move"
225
+ },
226
+ {
227
+ "x": 1978,
228
+ "y": 1042,
229
+ "timestamp": 3675,
83
230
  "cursorType": "default",
84
- "type": "mouseup"
231
+ "type": "move"
85
232
  },
86
233
  {
87
- "x": 1176,
88
- "y": 764,
89
- "timestamp": 2744,
234
+ "x": 1983,
235
+ "y": 1061,
236
+ "timestamp": 3693,
90
237
  "cursorType": "default",
91
238
  "type": "move"
239
+ },
240
+ {
241
+ "x": 1986,
242
+ "y": 1072,
243
+ "timestamp": 3713,
244
+ "cursorType": "text",
245
+ "type": "move"
246
+ },
247
+ {
248
+ "x": 1990,
249
+ "y": 1082,
250
+ "timestamp": 3734,
251
+ "cursorType": "text",
252
+ "type": "move"
253
+ },
254
+ {
255
+ "x": 1991,
256
+ "y": 1086,
257
+ "timestamp": 3755,
258
+ "cursorType": "text",
259
+ "type": "move"
260
+ },
261
+ {
262
+ "x": 1993,
263
+ "y": 1091,
264
+ "timestamp": 3776,
265
+ "cursorType": "text",
266
+ "type": "move"
267
+ },
268
+ {
269
+ "x": 1994,
270
+ "y": 1096,
271
+ "timestamp": 3797,
272
+ "cursorType": "text",
273
+ "type": "move"
274
+ },
275
+ {
276
+ "x": 1995,
277
+ "y": 1099,
278
+ "timestamp": 3818,
279
+ "cursorType": "text",
280
+ "type": "move"
281
+ },
282
+ {
283
+ "x": 1997,
284
+ "y": 1106,
285
+ "timestamp": 3841,
286
+ "cursorType": "text",
287
+ "type": "move"
288
+ },
289
+ {
290
+ "x": 1997,
291
+ "y": 1109,
292
+ "timestamp": 3859,
293
+ "cursorType": "text",
294
+ "type": "move"
295
+ },
296
+ {
297
+ "x": 1998,
298
+ "y": 1116,
299
+ "timestamp": 3882,
300
+ "cursorType": "text",
301
+ "type": "move"
302
+ },
303
+ {
304
+ "x": 2000,
305
+ "y": 1120,
306
+ "timestamp": 3902,
307
+ "cursorType": "text",
308
+ "type": "move"
309
+ },
310
+ {
311
+ "x": 2000,
312
+ "y": 1123,
313
+ "timestamp": 3924,
314
+ "cursorType": "text",
315
+ "type": "move"
316
+ },
317
+ {
318
+ "x": 2001,
319
+ "y": 1126,
320
+ "timestamp": 3945,
321
+ "cursorType": "text",
322
+ "type": "move"
323
+ },
324
+ {
325
+ "x": 2001,
326
+ "y": 1129,
327
+ "timestamp": 3967,
328
+ "cursorType": "text",
329
+ "type": "move"
330
+ },
331
+ {
332
+ "x": 2002,
333
+ "y": 1135,
334
+ "timestamp": 3986,
335
+ "cursorType": "pointer",
336
+ "type": "move"
337
+ },
338
+ {
339
+ "x": 2002,
340
+ "y": 1142,
341
+ "timestamp": 4007,
342
+ "cursorType": "pointer",
343
+ "type": "move"
344
+ },
345
+ {
346
+ "x": 2002,
347
+ "y": 1146,
348
+ "timestamp": 4027,
349
+ "cursorType": "pointer",
350
+ "type": "move"
351
+ },
352
+ {
353
+ "x": 2002,
354
+ "y": 1151,
355
+ "timestamp": 4049,
356
+ "cursorType": "pointer",
357
+ "type": "move"
92
358
  }
93
359
  ]
package/cursor-test.js CHANGED
@@ -1,50 +1,22 @@
1
- const MacRecorder = require("./index");
2
- const path = require("path");
3
- const fs = require("fs");
4
-
5
- async function testCursorCapture() {
6
- console.log("šŸŽÆ Cursor Capture Demo\n");
7
-
8
- const recorder = new MacRecorder();
9
- const outputPath = path.join(__dirname, "cursor-data.json");
10
-
11
- try {
12
- // Başlat
13
- await recorder.startCursorCapture(outputPath);
14
- console.log("āœ… Kayıt başladı...");
15
-
16
- // 5 saniye bekle
17
- console.log("šŸ“± 5 saniye hareket ettirin, tıklayın...");
18
-
19
- for (let i = 5; i > 0; i--) {
20
- process.stdout.write(`ā³ ${i}... `);
21
- await new Promise((resolve) => setTimeout(resolve, 1000));
22
- }
23
- console.log("\n");
24
-
25
- // Durdur
26
- await recorder.stopCursorCapture();
27
- console.log("āœ… Kayıt tamamlandı!");
28
-
29
- // SonuƧ
30
- if (fs.existsSync(outputPath)) {
31
- const data = JSON.parse(fs.readFileSync(outputPath, "utf8"));
32
- console.log(`šŸ“„ ${data.length} event kaydedildi -> ${outputPath}`);
33
-
34
- // Basit istatistik
35
- const clicks = data.filter((d) => d.type === "mousedown").length;
36
- if (clicks > 0) {
37
- console.log(`šŸ–±ļø ${clicks} click algılandı`);
38
- }
39
- }
40
- } catch (error) {
41
- console.error("āŒ Hata:", error.message);
42
- }
1
+ const MacRecorder = require("./index.js");
2
+
3
+ const recorder = new MacRecorder();
4
+
5
+ console.log("Starting cursor tracking test...");
6
+ console.log(
7
+ "Move your cursor around different applications to test cursor type detection"
8
+ );
9
+ console.log("The test will run for 10 seconds");
10
+
11
+ try {
12
+ recorder.startCursorCapture("./cursor-data.json");
13
+
14
+ setTimeout(() => {
15
+ recorder.stopCursorCapture();
16
+ console.log("Test completed. Check cursor-data.json for results");
17
+ process.exit(0);
18
+ }, 10000);
19
+ } catch (error) {
20
+ console.error("Error during cursor tracking:", error);
21
+ process.exit(1);
43
22
  }
44
-
45
- // Direkt çalıştır
46
- if (require.main === module) {
47
- testCursorCapture().catch(console.error);
48
- }
49
-
50
- module.exports = { testCursorCapture };
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
- return new Promise((resolve, reject) => {
73
- try {
74
- const displays = nativeBinding.getDisplays();
75
- const formattedDisplays = displays.map((display, index) => ({
76
- id: index,
77
- name:
78
- typeof display === "string"
79
- ? display
80
- : display.name || `Display ${index + 1}`,
81
- resolution:
82
- typeof display === "object"
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
- displayId,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -43,47 +43,113 @@ static bool g_leftMouseDown = false;
43
43
  static bool g_rightMouseDown = false;
44
44
  static NSString *g_lastEventType = @"move";
45
45
 
46
- // Cursor type detection helper - gerƧek cursor type'ı al
46
+ // Event tap callback
47
+ static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *userInfo) {
48
+ return event;
49
+ }
50
+
51
+ // Cursor type detection helper - sistem genelindeki cursor type'ı al
47
52
  NSString* getCursorType() {
48
53
  @autoreleasepool {
49
54
  g_cursorTypeCounter++;
50
55
 
51
56
  @try {
52
- // NSCursor.currentCursor kullanarak gerƧek cursor type'ı al
53
- NSCursor *currentCursor = [NSCursor currentCursor];
57
+ // Get current cursor info
58
+ NSCursor *currentCursor = [NSCursor currentSystemCursor];
59
+ NSString *cursorType = @"default";
60
+
61
+ // Get cursor image info
62
+ NSImage *cursorImage = [currentCursor image];
63
+ NSPoint hotSpot = [currentCursor hotSpot];
64
+ NSSize imageSize = [cursorImage size];
54
65
 
55
- if (currentCursor == [NSCursor arrowCursor]) {
56
- g_lastDetectedCursorType = @"default";
57
- return @"default";
58
- } else if (currentCursor == [NSCursor pointingHandCursor]) {
59
- g_lastDetectedCursorType = @"pointer";
66
+ // Check cursor type by comparing with standard cursors
67
+ if ([currentCursor isEqual:[NSCursor pointingHandCursor]] ||
68
+ (hotSpot.x >= 5 && hotSpot.x <= 7 && hotSpot.y >= 0 && hotSpot.y <= 4) ||
69
+ (hotSpot.x >= 12 && hotSpot.x <= 14 && hotSpot.y >= 7 && hotSpot.y <= 9)) {
60
70
  return @"pointer";
61
- } else if (currentCursor == [NSCursor IBeamCursor]) {
62
- g_lastDetectedCursorType = @"text";
71
+ } else if ([currentCursor isEqual:[NSCursor IBeamCursor]] ||
72
+ (hotSpot.x >= 3 && hotSpot.x <= 5 && hotSpot.y >= 8 && hotSpot.y <= 10 &&
73
+ imageSize.width <= 10 && imageSize.height >= 16)) {
63
74
  return @"text";
64
- } else if (currentCursor == [NSCursor openHandCursor]) {
65
- g_lastDetectedCursorType = @"grab";
66
- return @"grab";
67
- } else if (currentCursor == [NSCursor closedHandCursor]) {
68
- g_lastDetectedCursorType = @"grabbing";
69
- return @"grabbing";
70
- } else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
71
- g_lastDetectedCursorType = @"ew-resize";
75
+ } else if ([currentCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
72
76
  return @"ew-resize";
73
- } else if (currentCursor == [NSCursor resizeUpDownCursor]) {
74
- g_lastDetectedCursorType = @"ns-resize";
77
+ } else if ([currentCursor isEqual:[NSCursor resizeUpDownCursor]]) {
75
78
  return @"ns-resize";
76
- } else if (currentCursor == [NSCursor crosshairCursor]) {
77
- g_lastDetectedCursorType = @"crosshair";
78
- return @"crosshair";
79
- } else {
80
- // Bilinmeyen cursor - default olarak dƶn
81
- g_lastDetectedCursorType = @"default";
82
- return @"default";
79
+ } else if ([currentCursor isEqual:[NSCursor openHandCursor]] ||
80
+ [currentCursor isEqual:[NSCursor closedHandCursor]]) {
81
+ return @"grabbing";
83
82
  }
83
+
84
+ // Check if we're in a drag operation
85
+ CGEventRef event = CGEventCreate(NULL);
86
+ if (event) {
87
+ CGEventType eventType = (CGEventType)CGEventGetType(event);
88
+ if (eventType == kCGEventLeftMouseDragged ||
89
+ eventType == kCGEventRightMouseDragged) {
90
+ CFRelease(event);
91
+ return @"grabbing";
92
+ }
93
+ CFRelease(event);
94
+ }
95
+
96
+ // Get the window under the cursor
97
+ CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
98
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
99
+ AXUIElementRef elementAtPosition = NULL;
100
+ AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
101
+
102
+ if (error == kAXErrorSuccess && elementAtPosition) {
103
+ CFStringRef role = NULL;
104
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
105
+
106
+ if (error == kAXErrorSuccess && role) {
107
+ NSString *elementRole = (__bridge_transfer NSString*)role;
108
+
109
+ // Check for clickable elements that should show pointer cursor
110
+ if ([elementRole isEqualToString:@"AXLink"] ||
111
+ [elementRole isEqualToString:@"AXButton"] ||
112
+ [elementRole isEqualToString:@"AXMenuItem"] ||
113
+ [elementRole isEqualToString:@"AXRadioButton"] ||
114
+ [elementRole isEqualToString:@"AXCheckBox"]) {
115
+ return @"pointer";
116
+ }
117
+
118
+ // Check subrole for additional pointer cursor elements
119
+ CFStringRef subrole = NULL;
120
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
121
+ if (error == kAXErrorSuccess && subrole) {
122
+ NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
123
+
124
+ if ([elementSubrole isEqualToString:@"AXClickable"] ||
125
+ [elementSubrole isEqualToString:@"AXDisclosureTriangle"] ||
126
+ [elementSubrole isEqualToString:@"AXToolbarButton"] ||
127
+ [elementSubrole isEqualToString:@"AXCloseButton"] ||
128
+ [elementSubrole isEqualToString:@"AXMinimizeButton"] ||
129
+ [elementSubrole isEqualToString:@"AXZoomButton"]) {
130
+ return @"pointer";
131
+ }
132
+ }
133
+
134
+ // Check for text elements
135
+ if ([elementRole isEqualToString:@"AXTextField"] ||
136
+ [elementRole isEqualToString:@"AXTextArea"] ||
137
+ [elementRole isEqualToString:@"AXStaticText"]) {
138
+ return @"text";
139
+ }
140
+ }
141
+
142
+ CFRelease(elementAtPosition);
143
+ }
144
+
145
+ if (systemWide) {
146
+ CFRelease(systemWide);
147
+ }
148
+
149
+ return cursorType;
150
+
84
151
  } @catch (NSException *exception) {
85
- // Hata durumunda default dƶn
86
- g_lastDetectedCursorType = @"default";
152
+ NSLog(@"Error in getCursorType: %@", exception);
87
153
  return @"default";
88
154
  }
89
155
  }
@@ -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
- NSMutableArray *displays = [NSMutableArray array];
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
- free(displayList);
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
- CGContextDrawImage(context, CGRectMake(0, 0, thumbnailWidth, thumbnailHeight), displayImage);
572
- CGImageRef thumbnailImage = CGBitmapContextCreateImage(context);
573
-
574
- if (thumbnailImage) {
575
- // Convert to PNG data
576
- NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:thumbnailImage];
577
- NSData *pngData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
578
-
579
- if (pngData) {
580
- // Convert to Base64
581
- NSString *base64String = [pngData base64EncodedStringWithOptions:0];
582
- std::string base64Std = [base64String UTF8String];
583
-
584
- CGImageRelease(thumbnailImage);
585
- CGContextRelease(context);
586
- CGColorSpaceRelease(colorSpace);
587
- CGImageRelease(displayImage);
588
-
589
- return Napi::String::New(env, base64Std);
590
- }
591
-
592
- CGImageRelease(thumbnailImage);
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.Null();
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