@aspiresys/visor 1.2.9 → 1.2.11

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/dist/index.d.ts CHANGED
@@ -1,11 +1,129 @@
1
1
  import { Key, Button, Point } from "@nut-tree-fork/nut-js";
2
+ import { Region } from "./types";
2
3
  export declare class Visor {
3
- mouse: import("@nut-tree-fork/nut-js").MouseClass;
4
- keyboard: import("@nut-tree-fork/nut-js").KeyboardClass;
4
+ private mouse;
5
+ private keyboard;
5
6
  Key: typeof Key;
6
7
  Button: typeof Button;
7
8
  private configWarningShown;
8
9
  private checkConfig;
10
+ /**
11
+ * Moves the mouse cursor to the
12
+ * center of a specific screen region.
13
+ *
14
+ * Region coordinates typically come from:
15
+ * - visor.find()
16
+ * - visor.findAll()
17
+ * - OCR text matching
18
+ * - Visor Inspector
19
+ *
20
+ * Display scaling is automatically
21
+ * applied using the configured
22
+ * scale factor.
23
+ *
24
+ * @param region Screen region coordinates.
25
+ * @param offset Offset coords.
26
+ *
27
+ * @example
28
+ * await visor.moveToRegion({
29
+ * x: 100,
30
+ * y: 200,
31
+ * width: 150,
32
+ * height: 50
33
+ * });
34
+ */
35
+ moveToRegion(region: Region, offset?: {
36
+ x: number;
37
+ y: number;
38
+ }): Promise<void>;
39
+ /**
40
+ * Performs a left mouse click at the
41
+ * center of a specific screen region.
42
+ *
43
+ * Region coordinates typically come from:
44
+ * - visor.find()
45
+ * - visor.findAll()
46
+ * - OCR text matching
47
+ * - Visor Inspector
48
+ *
49
+ * Display scaling is automatically
50
+ * applied using the configured
51
+ * scale factor.
52
+ *
53
+ * @param region Screen region coordinates.
54
+ * @param offset Offset coords.
55
+ *
56
+ * @example
57
+ * await visor.clickRegion({
58
+ * x: 100,
59
+ * y: 200,
60
+ * width: 150,
61
+ * height: 50
62
+ * });
63
+ */
64
+ clickRegion(region: Region, offset?: {
65
+ x: number;
66
+ y: number;
67
+ }): Promise<void>;
68
+ /**
69
+ * Performs a double left mouse click
70
+ * at the center of a specific
71
+ * screen region.
72
+ *
73
+ * Region coordinates typically come from:
74
+ * - visor.find()
75
+ * - visor.findAll()
76
+ * - OCR text matching
77
+ * - Visor Inspector
78
+ *
79
+ * Display scaling is automatically
80
+ * applied using the configured
81
+ * scale factor.
82
+ *
83
+ * @param region Screen region coordinates.
84
+ * @param offset Offset coords.
85
+ *
86
+ * @example
87
+ * await visor.doubleClickRegion({
88
+ * x: 100,
89
+ * y: 200,
90
+ * width: 150,
91
+ * height: 50
92
+ * });
93
+ */
94
+ doubleClickRegion(region: Region, offset?: {
95
+ x: number;
96
+ y: number;
97
+ }): Promise<void>;
98
+ /**
99
+ * Performs a right mouse click at the
100
+ * center of a specific screen region.
101
+ *
102
+ * Region coordinates typically come from:
103
+ * - visor.find()
104
+ * - visor.findAll()
105
+ * - OCR text matching
106
+ * - Visor Inspector
107
+ *
108
+ * Display scaling is automatically
109
+ * applied using the configured
110
+ * scale factor.
111
+ *
112
+ * @param region Screen region coordinates.
113
+ * @param offset Offset coords.
114
+ *
115
+ * @example
116
+ * await visor.rightClickRegion({
117
+ * x: 100,
118
+ * y: 200,
119
+ * width: 150,
120
+ * height: 50
121
+ * });
122
+ */
123
+ rightClickRegion(region: Region, offset?: {
124
+ x: number;
125
+ y: number;
126
+ }): Promise<void>;
9
127
  /**
10
128
  * Finds an image on screen using OpenCV
11
129
  * and performs a left mouse click
@@ -126,7 +244,7 @@ export declare class Visor {
126
244
  * const region =
127
245
  * await visor.find("save.png");
128
246
  */
129
- find(image: string, confidence?: number): Promise<import("./types").Region>;
247
+ find(image: string, confidence?: number): Promise<Region>;
130
248
  /**
131
249
  * Checks whether an image exists
132
250
  * on the current screen.
@@ -169,7 +287,7 @@ export declare class Visor {
169
287
  * const matches =
170
288
  * await visor.findAll("icon.png");
171
289
  */
172
- findAll(image: string, confidence?: number): Promise<import("./types").Region[]>;
290
+ findAll(image: string, confidence?: number): Promise<Region[]>;
173
291
  /**
174
292
  * Finds all image matches and prints
175
293
  * a formatted match preview including
@@ -192,7 +310,7 @@ export declare class Visor {
192
310
  * @example
193
311
  * await visor.previewMatches("button.png");
194
312
  */
195
- previewMatches(image: string, confidence?: number): Promise<import("./types").Region[]>;
313
+ previewMatches(image: string, confidence?: number): Promise<Region[]>;
196
314
  /**
197
315
  * Waits until an image appears
198
316
  * on the screen.
@@ -342,7 +460,7 @@ export declare class Visor {
342
460
  y: number;
343
461
  width: number;
344
462
  height: number;
345
- }): Promise<import("./types").Region>;
463
+ }): Promise<Region>;
346
464
  /**
347
465
  * Checks whether specific text exists
348
466
  * on screen using OCR.
@@ -601,11 +719,12 @@ export declare class Visor {
601
719
  *
602
720
  * @param x X coordinate.
603
721
  * @param y Y coordinate.
722
+ * @param inspectorCoords Boolean to represent if the coords are from visor-inspector
604
723
  *
605
724
  * @example
606
725
  * await visor.moveMouse(500, 300);
607
726
  */
608
- moveMouse(x: number, y: number): Promise<void>;
727
+ moveMouse(x: number, y: number, inspectorCoords?: boolean): Promise<void>;
609
728
  /**
610
729
  * Returns the current mouse cursor
611
730
  * position.
@@ -783,7 +902,7 @@ export declare class Visor {
783
902
  */
784
903
  findAny(images: string[], confidence?: number): Promise<{
785
904
  image: string;
786
- region: import("./types").Region;
905
+ region: Region;
787
906
  }>;
788
907
  /**
789
908
  * Checks whether any image from
package/dist/index.js CHANGED
@@ -48,6 +48,134 @@ class Visor {
48
48
  this.configWarningShown = true;
49
49
  }
50
50
  }
51
+ /**
52
+ * Moves the mouse cursor to the
53
+ * center of a specific screen region.
54
+ *
55
+ * Region coordinates typically come from:
56
+ * - visor.find()
57
+ * - visor.findAll()
58
+ * - OCR text matching
59
+ * - Visor Inspector
60
+ *
61
+ * Display scaling is automatically
62
+ * applied using the configured
63
+ * scale factor.
64
+ *
65
+ * @param region Screen region coordinates.
66
+ * @param offset Offset coords.
67
+ *
68
+ * @example
69
+ * await visor.moveToRegion({
70
+ * x: 100,
71
+ * y: 200,
72
+ * width: 150,
73
+ * height: 50
74
+ * });
75
+ */
76
+ async moveToRegion(region, offset = {
77
+ x: 0,
78
+ y: 0
79
+ }) {
80
+ await (0, mouse_1.moveToRegion)(region, offset);
81
+ }
82
+ /**
83
+ * Performs a left mouse click at the
84
+ * center of a specific screen region.
85
+ *
86
+ * Region coordinates typically come from:
87
+ * - visor.find()
88
+ * - visor.findAll()
89
+ * - OCR text matching
90
+ * - Visor Inspector
91
+ *
92
+ * Display scaling is automatically
93
+ * applied using the configured
94
+ * scale factor.
95
+ *
96
+ * @param region Screen region coordinates.
97
+ * @param offset Offset coords.
98
+ *
99
+ * @example
100
+ * await visor.clickRegion({
101
+ * x: 100,
102
+ * y: 200,
103
+ * width: 150,
104
+ * height: 50
105
+ * });
106
+ */
107
+ async clickRegion(region, offset = {
108
+ x: 0,
109
+ y: 0
110
+ }) {
111
+ await (0, mouse_1.moveToRegion)(region, offset);
112
+ await this.mouse.click(this.Button.LEFT);
113
+ }
114
+ /**
115
+ * Performs a double left mouse click
116
+ * at the center of a specific
117
+ * screen region.
118
+ *
119
+ * Region coordinates typically come from:
120
+ * - visor.find()
121
+ * - visor.findAll()
122
+ * - OCR text matching
123
+ * - Visor Inspector
124
+ *
125
+ * Display scaling is automatically
126
+ * applied using the configured
127
+ * scale factor.
128
+ *
129
+ * @param region Screen region coordinates.
130
+ * @param offset Offset coords.
131
+ *
132
+ * @example
133
+ * await visor.doubleClickRegion({
134
+ * x: 100,
135
+ * y: 200,
136
+ * width: 150,
137
+ * height: 50
138
+ * });
139
+ */
140
+ async doubleClickRegion(region, offset = {
141
+ x: 0,
142
+ y: 0
143
+ }) {
144
+ await (0, mouse_1.moveToRegion)(region, offset);
145
+ await this.mouse.doubleClick(this.Button.LEFT);
146
+ }
147
+ /**
148
+ * Performs a right mouse click at the
149
+ * center of a specific screen region.
150
+ *
151
+ * Region coordinates typically come from:
152
+ * - visor.find()
153
+ * - visor.findAll()
154
+ * - OCR text matching
155
+ * - Visor Inspector
156
+ *
157
+ * Display scaling is automatically
158
+ * applied using the configured
159
+ * scale factor.
160
+ *
161
+ * @param region Screen region coordinates.
162
+ * @param offset Offset coords.
163
+ *
164
+ * @example
165
+ * await visor.rightClickRegion({
166
+ * x: 100,
167
+ * y: 200,
168
+ * width: 150,
169
+ * height: 50
170
+ * });
171
+ */
172
+ async rightClickRegion(region, offset = {
173
+ x: 0,
174
+ y: 0
175
+ }) {
176
+ await (0, mouse_1.moveToRegion)(region, offset);
177
+ await this.mouse.click(this.Button.RIGHT);
178
+ }
51
179
  /**
52
180
  * Finds an image on screen using OpenCV
53
181
  * and performs a left mouse click
@@ -208,8 +336,12 @@ class Visor {
208
336
  const start = Date.now();
209
337
  const screen = await (0, screen_1.captureScreen)();
210
338
  const template = await (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
339
+ const scaleFactor = config_1.visorConfig.scaleFactor;
211
340
  try {
212
341
  const result = (0, matcher_1.matchTemplate)(screen, template, confidence);
342
+ if (result) {
343
+ (0, logger_1.log)(`[FIND] Actual coords: (${Math.floor(result.x * scaleFactor)}, ${Math.floor(result.y * scaleFactor)})`);
344
+ }
213
345
  (0, logger_1.log)(`[FIND] Total: ${Date.now() - start} ms`);
214
346
  return result;
215
347
  }
@@ -801,13 +933,19 @@ CONF:${m.confidence.toFixed(3)}
801
933
  *
802
934
  * @param x X coordinate.
803
935
  * @param y Y coordinate.
936
+ * @param inspectorCoords Boolean to represent if the coords are from visor-inspector
804
937
  *
805
938
  * @example
806
939
  * await visor.moveMouse(500, 300);
807
940
  */
808
- async moveMouse(x, y) {
941
+ async moveMouse(x, y, inspectorCoords = false) {
809
942
  this.checkConfig();
810
- await this.mouse.move((0, nut_js_1.straightTo)(new nut_js_1.Point(x, y)));
943
+ if (inspectorCoords) {
944
+ await this.mouse.move((0, nut_js_1.straightTo)(new nut_js_1.Point(x, y)));
945
+ return;
946
+ }
947
+ const scaleFactor = config_1.visorConfig.scaleFactor;
948
+ await this.mouse.move((0, nut_js_1.straightTo)(new nut_js_1.Point(Math.floor(x / scaleFactor), Math.floor(y / scaleFactor))));
811
949
  }
812
950
  /**
813
951
  * Returns the current mouse cursor
@@ -24,7 +24,8 @@
24
24
  <div class="input-wrapper">
25
25
  <span class="input-hint">Confidence</span>
26
26
  <input id="confidenceInput" type="number" min="0" max="1" step="0.01" value="0.8" />
27
- <span class="input-hint">(0.0 - 1.0)</span>
27
+ <span class="input-hint">(0.0 - 1.0) </span>
28
+ Current Template: <span id="currentTemplate" class="template-status">None</span>
28
29
  </div>
29
30
  </div>
30
31
  </div>
@@ -32,26 +33,16 @@
32
33
  </section>
33
34
  <div class="capture-layout">
34
35
  <div class="capture-panel">
35
- <h3 class="section-title"> Current Capture </h3>
36
+ <h3 class="section-title"> Current Capture (X:<span id="mousex"></span>, Y:<span id="mousey"></span>)</h3>
36
37
  <canvas id="screenCanvas" class="canvas"></canvas>
37
38
  </div>
38
39
  <div class="result-panel">
40
+ <h3 class="section-title"> Template </h3>
41
+ <img id="templatePreview" class="template-preview" alt="No Template Loaded" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="/>
39
42
  <h3 class="section-title"> Match Result </h3>
40
43
  <div id="matchResult"> No Match Tested </div>
41
- </div>
44
+ </div>
42
45
  </div>
43
- <section class="template-section">
44
- <h3 class="section-title">Template Management</h3>
45
- <div class="template-grid">
46
- <div class="template-info">
47
- <p>
48
- <strong>Current Template: </strong>
49
- <span id="currentTemplate" class="template-status">None</span>
50
- </p>
51
- </div>
52
- <img id="templatePreview" class="template-preview" />
53
- </div>
54
- </section>
55
46
  </div>
56
47
  <script src="./src/renderer.js"></script>
57
48
  </body>
@@ -30,6 +30,7 @@ captureBtn.addEventListener("click",
30
30
  const scale = image.width > maxWidth ? maxWidth / image.width : 1;
31
31
  canvas.width = image.width * scale;
32
32
  canvas.height = image.height * scale;
33
+ matchResult.textContent = " No Match Tested ";
33
34
  ctx.drawImage(
34
35
  image,
35
36
  0,
@@ -58,6 +59,8 @@ canvas.addEventListener("mousedown",
58
59
  canvas.addEventListener("mousemove",
59
60
  (e) => {
60
61
  if (!selecting) {
62
+ document.getElementById("mousex").innerHTML = Math.round(e.offsetX * imageScaleX);
63
+ document.getElementById("mousey").innerHTML = Math.round(e.offsetY * imageScaleY);
61
64
  return;
62
65
  }
63
66
  displayEndX = e.offsetX;
@@ -95,6 +98,7 @@ canvas.addEventListener("mouseup",
95
98
  }
96
99
  );
97
100
 
101
+
98
102
  saveBtn.addEventListener("click",
99
103
  async () => {
100
104
  if (
@@ -122,8 +126,18 @@ saveBtn.addEventListener("click",
122
126
  height: Math.abs(endY - startY),
123
127
  outputPath: filePath
124
128
  });
129
+ const fileName = filePath.split(/[\\/]/).pop();
125
130
  selectedTemplatePath = filePath;
126
- currentTemplate.textContent = filePath;
131
+ currentTemplate.textContent = fileName;
132
+ currentTemplate.title = filePath;
133
+ matchResult.textContent = " No Match Tested ";
134
+ ctx.drawImage(
135
+ image,
136
+ 0,
137
+ 0,
138
+ canvas.width,
139
+ canvas.height
140
+ );
127
141
  templatePreview.src = filePath + "?t=" + Date.now();
128
142
  alert("Template Saved");
129
143
  }
@@ -152,8 +166,30 @@ testMatchBtn.addEventListener("click", async () => {
152
166
 
153
167
  if (!result) {
154
168
  matchResult.textContent = "✗ MATCH NOT FOUND";
169
+ ctx.drawImage(
170
+ image,
171
+ 0,
172
+ 0,
173
+ canvas.width,
174
+ canvas.height
175
+ );
155
176
  return;
156
177
  }
178
+ ctx.drawImage(
179
+ image,
180
+ 0,
181
+ 0,
182
+ canvas.width,
183
+ canvas.height
184
+ );
185
+ ctx.strokeStyle = "lime";
186
+ ctx.lineWidth = 3;
187
+ ctx.strokeRect(
188
+ result.x / imageScaleX,
189
+ result.y / imageScaleY,
190
+ result.width / imageScaleX,
191
+ result.height / imageScaleY
192
+ );
157
193
  matchResult.textContent = `
158
194
  ✓ MATCH FOUND
159
195
 
@@ -175,8 +211,18 @@ loadTemplateBtn.addEventListener("click",
175
211
  if (!path) {
176
212
  return;
177
213
  }
214
+ const fileName = path.split(/[\\/]/).pop();
178
215
  selectedTemplatePath = path;
179
- currentTemplate.textContent = path;
216
+ currentTemplate.textContent = fileName;
217
+ currentTemplate.title = path;
218
+ matchResult.textContent = " No Match Tested ";
219
+ ctx.drawImage(
220
+ image,
221
+ 0,
222
+ 0,
223
+ canvas.width,
224
+ canvas.height
225
+ );
180
226
  templatePreview.src = path + "?t=" + Date.now();
181
227
  }
182
228
  );
@@ -238,6 +238,7 @@ input[type="number"]:focus {
238
238
  display: block;
239
239
  margin-top: 15px;
240
240
  box-shadow: 0 0 20px rgba(0, 212, 255, 0.1);
241
+ width: stretch;
241
242
  }
242
243
 
243
244
  /* TEMPLATE SECTION */
@@ -268,26 +269,23 @@ input[type="number"]:focus {
268
269
  }
269
270
 
270
271
  .template-status {
271
- display: inline-block;
272
- padding: 4px 7px;
273
- background: rgba(76, 175, 80, 0.15);
274
- border: 1px solid rgba(76, 175, 80, 0.3);
275
- border-radius: 6px;
276
272
  color: #4caf50;
277
- font-weight: 500;
278
- width: fit-content;
273
+ font-size: small;
279
274
  }
280
275
 
281
276
  .template-preview {
282
277
  border: 2px solid #00d4ff;
283
278
  border-radius: 8px;
279
+ width: 130px;
284
280
  max-width: 100%;
285
- height: auto;
281
+ height: 130px;
286
282
  max-height: 150px;
287
283
  object-fit: contain;
288
284
  background: #0a0a0a;
289
285
  box-shadow: 0 0 20px rgba(0, 212, 255, 0.1);
290
286
  display: none;
287
+ margin-top: 15px;
288
+ margin-bottom: 15px;
291
289
  }
292
290
 
293
291
  .template-preview[src]:not([src=""]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspiresys/visor",
3
- "version": "1.2.9",
3
+ "version": "1.2.11",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -22,6 +22,8 @@ Visor is designed for automating desktop workflows using visual interactions ins
22
22
  * Region OCR support
23
23
  * Automatic display scaling detection
24
24
  * Mouse automation
25
+ * Region-based mouse automation
26
+ * Target offset support
25
27
  * Keyboard automation
26
28
  * Drag & drop support
27
29
  * Multi-theme image handling
@@ -51,6 +53,23 @@ npm install @aspiresys/visor
51
53
 
52
54
  ---
53
55
 
56
+ # Visor Inspector
57
+
58
+ Visor includes an optional desktop Inspector tool for:
59
+
60
+ * Capturing templates
61
+ * Testing image matches
62
+ * Measuring screen coordinates
63
+ * Validating confidence thresholds
64
+
65
+ Run:
66
+
67
+ ```bash
68
+ npx visor-inspector
69
+ ```
70
+
71
+ ---
72
+
54
73
  # Quick Start
55
74
 
56
75
  ```ts
@@ -167,6 +186,67 @@ const region =
167
186
 
168
187
  ---
169
188
 
189
+ ## Region-Based Automation
190
+
191
+ Regions can be obtained from:
192
+
193
+ * visor.find()
194
+ * visor.findAll()
195
+ * visor.findText()
196
+ * Visor Inspector
197
+
198
+ ### Move To Region
199
+
200
+ ```ts
201
+ const region =
202
+ await visor.find(
203
+ "save.png"
204
+ );
205
+
206
+ await visor.moveToRegion(
207
+ region
208
+ );
209
+ ```
210
+
211
+ ### Click Region
212
+
213
+ ```ts
214
+ const region =
215
+ await visor.find(
216
+ "save.png"
217
+ );
218
+
219
+ await visor.clickRegion(
220
+ region
221
+ );
222
+ ```
223
+
224
+ ### Double Click Region
225
+
226
+ ```ts
227
+ await visor.doubleClickRegion({
228
+ x: 100,
229
+ y: 200,
230
+ width: 150,
231
+ height: 50
232
+ });
233
+ ```
234
+
235
+ ### Right Click Region
236
+
237
+ ```ts
238
+ await visor.rightClickRegion({
239
+ x: 100,
240
+ y: 200,
241
+ width: 150,
242
+ height: 50
243
+ });
244
+ ```
245
+
246
+ Display scaling is automatically applied when using region-based APIs.
247
+
248
+ ---
249
+
170
250
  ## Check Image Exists
171
251
 
172
252
  ```ts
@@ -230,6 +310,47 @@ await visor.hover("menu.png");
230
310
 
231
311
  ---
232
312
 
313
+ ## Target Offsets
314
+
315
+ Target offsets allow mouse actions to be performed relative to the center of a matched image.
316
+
317
+ Useful for:
318
+
319
+ * Dropdown arrows
320
+ * Adjacent controls
321
+ * Dynamic layouts
322
+ * Composite UI elements
323
+
324
+ ### Click With Offset
325
+
326
+ ```ts
327
+ await visor.click(
328
+ "search.png",
329
+ 0.8,
330
+ {
331
+ x: 50,
332
+ y: 0
333
+ }
334
+ );
335
+ ```
336
+
337
+ ### Hover With Offset
338
+
339
+ ```ts
340
+ await visor.hover(
341
+ "menu.png",
342
+ 0.8,
343
+ {
344
+ x: -20,
345
+ y: 10
346
+ }
347
+ );
348
+ ```
349
+
350
+ Offsets are applied relative to the center of the matched region before display scaling adjustments are performed.
351
+
352
+ ---
353
+
233
354
  # OCR Automation
234
355
 
235
356
  Visor includes OCR automation powered by Tesseract.js.
@@ -351,6 +472,21 @@ await visor.moveMouse(
351
472
 
352
473
  ---
353
474
 
475
+ ### Move To Inspector Region
476
+
477
+ ```ts
478
+ await visor.moveToRegion({
479
+ x: 90,
480
+ y: 61,
481
+ width: 138,
482
+ height: 69
483
+ });
484
+ ```
485
+
486
+ Region coordinates can be copied directly from Visor Inspector match results.
487
+
488
+ ---
489
+
354
490
  ## Scroll Down
355
491
 
356
492
  ```ts
@@ -497,9 +633,9 @@ visor.loadConfig({
497
633
 
498
634
  # Roadmap
499
635
 
500
- * Template cache
501
- * OCR cache
502
- * Scale cache
636
+ * Match visualization overlay
637
+ * Inspector coordinate picker
638
+ * Multi-monitor support improvements
503
639
  * Parallel image matching
504
640
  * Advanced OCR tuning
505
641
  * Electron recorder
Binary file