diff --git a/src/handlers/tools.zod.ts b/src/handlers/tools.zod.ts
index 67d48eb..ec8b8b7 100644
--- a/src/handlers/tools.zod.ts
+++ b/src/handlers/tools.zod.ts
@@ -93,6 +93,19 @@ export function setupTools(server: Server, provider: AutomationProvider): void {
default: { width: 1280, fit: 'contain' },
description: 'Resize options for the screenshot',
},
+ grid: {
+ oneOf: [{ type: 'boolean' }, { type: 'number', minimum: 10, maximum: 500 }],
+ default: false,
+ description:
+ 'Draw coordinate grid overlay (true for 100px spacing, or specify pixel spacing)',
+ },
+ gridTransparency: {
+ type: 'number',
+ minimum: 0,
+ maximum: 100,
+ default: 50,
+ description: 'Grid line transparency (0 = fully transparent, 100 = fully opaque)',
+ },
},
},
},
diff --git a/src/providers/keysender/screen.ts b/src/providers/keysender/screen.ts
index 55b76f9..ebe7ced 100644
--- a/src/providers/keysender/screen.ts
+++ b/src/providers/keysender/screen.ts
@@ -143,7 +143,9 @@ export class KeysenderScreenAutomation implements ScreenAutomation {
handle: number;
};
- this.logger.warn(`Using fallback window "${fallbackWindow.title}" with default view values`);
+ this.logger.warn(
+ `Using fallback window "${fallbackWindow.title}" with default view values`,
+ );
return {
window: fallbackWindow,
@@ -427,7 +429,7 @@ export class KeysenderScreenAutomation implements ScreenAutomation {
(Math.abs(updatedView.width - width) > 20 || Math.abs(updatedView.height - height) > 20)
) {
this.logger.warn(
- `Resize may not have been successful. Requested: ${width}x${height}, Got: ${updatedView.width}x${updatedView.height}`
+ `Resize may not have been successful. Requested: ${width}x${height}, Got: ${updatedView.width}x${updatedView.height}`,
);
} else if (
operationType === 'reposition' &&
@@ -436,7 +438,7 @@ export class KeysenderScreenAutomation implements ScreenAutomation {
(Math.abs(updatedView.x - x) > 20 || Math.abs(updatedView.y - y) > 20)
) {
this.logger.warn(
- `Repositioning may not have been successful. Requested: (${x}, ${y}), Got: (${updatedView.x}, ${updatedView.y})`
+ `Repositioning may not have been successful. Requested: (${x}, ${y}), Got: (${updatedView.x}, ${updatedView.y})`,
);
}
} catch (viewError) {
@@ -599,6 +601,54 @@ export class KeysenderScreenAutomation implements ScreenAutomation {
});
}
+ // Draw grid overlay if requested
+ if (mergedOptions.grid) {
+ const gridSpacing = typeof mergedOptions.grid === 'number' ? mergedOptions.grid : 100;
+
+ // Calculate final image dimensions from resize settings
+ const targetWidth = mergedOptions.resize?.width || 1280;
+ const imgWidth = Math.min(width, targetWidth);
+ const imgHeight = Math.round(height * (imgWidth / width));
+
+ // Get window position offset to show true screen coordinates
+ let offsetX = 0;
+ let offsetY = 0;
+ try {
+ const viewInfo = this.hardware.workwindow.getView();
+ offsetX = viewInfo.x || 0;
+ offsetY = viewInfo.y || 0;
+ } catch {
+ // If we can't get window position, use 0 offset (full screen capture)
+ }
+
+ // Calculate scale factor (image pixels to screen pixels)
+ const scaleX = width / imgWidth;
+ const scaleY = height / imgHeight;
+
+ // Calculate grid opacity from transparency (0-100 -> 0-1)
+ const gridOpacity = (mergedOptions.gridTransparency ?? 50) / 100;
+ const textOpacity = Math.min(1, gridOpacity + 0.3); // Text slightly more visible
+
+ // Build SVG grid with screen coordinates (accounting for window offset and scale)
+ let svgLines = '';
+ for (let x = gridSpacing; x < imgWidth; x += gridSpacing) {
+ const screenX = Math.round(offsetX + x * scaleX);
+ svgLines += ``;
+ svgLines += `${screenX}`;
+ }
+ for (let y = gridSpacing; y < imgHeight; y += gridSpacing) {
+ const screenY = Math.round(offsetY + y * scaleY);
+ svgLines += ``;
+ svgLines += `${screenY}`;
+ }
+
+ const svgOverlay = Buffer.from(
+ ``,
+ );
+
+ pipeline = pipeline.composite([{ input: svgOverlay, top: 0, left: 0 }]);
+ }
+
// Apply appropriate format-specific compression
if (mergedOptions.format === 'jpeg') {
pipeline = pipeline.jpeg({
diff --git a/src/tools/screenshot.test.ts b/src/tools/screenshot.test.ts
index 04a1295..61f345a 100644
--- a/src/tools/screenshot.test.ts
+++ b/src/tools/screenshot.test.ts
@@ -126,5 +126,59 @@ describe('Screenshot Functions', () => {
message: 'Failed to capture screenshot: Capture failed',
});
});
+
+ it('should pass grid options to provider', async () => {
+ // Setup mock provider
+ const mockProvider = {
+ screen: {
+ getScreenshot: vi.fn().mockResolvedValue({
+ success: true,
+ message: 'Screenshot captured successfully',
+ content: [
+ {
+ type: 'image',
+ data: 'test-image-with-grid',
+ mimeType: 'image/png',
+ },
+ ],
+ }),
+ },
+ };
+
+ vi.mocked(createAutomationProvider).mockReturnValue(mockProvider as any);
+
+ const options = {
+ grid: true,
+ gridTransparency: 75,
+ };
+
+ const result = await getScreenshot(options);
+
+ expect(mockProvider.screen.getScreenshot).toHaveBeenCalledWith(options);
+ expect(result.success).toBe(true);
+ });
+
+ it('should pass grid spacing as number to provider', async () => {
+ const mockProvider = {
+ screen: {
+ getScreenshot: vi.fn().mockResolvedValue({
+ success: true,
+ message: 'Screenshot captured successfully',
+ content: [{ type: 'image', data: 'test', mimeType: 'image/png' }],
+ }),
+ },
+ };
+
+ vi.mocked(createAutomationProvider).mockReturnValue(mockProvider as any);
+
+ const options = {
+ grid: 200, // Custom grid spacing
+ gridTransparency: 30,
+ };
+
+ await getScreenshot(options);
+
+ expect(mockProvider.screen.getScreenshot).toHaveBeenCalledWith(options);
+ });
});
});
diff --git a/src/tools/validation.zod.ts b/src/tools/validation.zod.ts
index 7743d59..da44990 100644
--- a/src/tools/validation.zod.ts
+++ b/src/tools/validation.zod.ts
@@ -306,6 +306,8 @@ export const ScreenshotOptionsSchema = z.object({
grayscale: z.boolean().optional(),
resize: ScreenshotResizeSchema.optional(),
compressionLevel: z.number().int().min(0).max(9).optional(),
+ grid: z.union([z.boolean(), z.number().int().min(10).max(500)]).optional(),
+ gridTransparency: z.number().int().min(0).max(100).optional(),
});
/**
diff --git a/src/types/common.ts b/src/types/common.ts
index d991519..708d78a 100644
--- a/src/types/common.ts
+++ b/src/types/common.ts
@@ -87,4 +87,6 @@ export interface ScreenshotOptions {
fit?: 'contain' | 'cover' | 'fill' | 'inside' | 'outside'; // Resize fit option
};
compressionLevel?: number; // PNG compression level (0-9), only used if format is 'png'
+ grid?: boolean | number; // Draw coordinate grid overlay (true for 100px, or specify spacing)
+ gridTransparency?: number; // Grid line transparency (0-100, default 50)
}