Skip to main content
Glama
GetScreenSize.ts7.35 kB
import { AdbUtils } from "../../utils/android-cmdline-tools/adb"; import { Axe } from "../../utils/ios-cmdline-tools/axe"; import { DeviceDetection } from "../../utils/deviceDetection"; import { logger } from "../../utils/logger"; import { BootedDevice, ExecResult, ScreenSize } from "../../models"; import * as fs from "fs"; import * as path from "path"; import * as crypto from "crypto"; export class GetScreenSize { private adb: AdbUtils; private axe: Axe; private readonly device: BootedDevice; private static memoryCache = new Map<string, ScreenSize>(); private static cacheDir = path.join(process.cwd(), ".cache", "screen-size"); /** * Create a Window instance * @param device - Optional device * @param adb - Optional AdbUtils instance for testing * @param axe - Optional IdbUtils instance for testing */ constructor(device: BootedDevice, adb: AdbUtils | null = null, axe: Axe | null = null) { this.device = device; this.adb = adb || new AdbUtils(device); this.axe = axe || new Axe(device); } /** * Generate cache key from deviceId * @param deviceId - Device identifier * @returns Hashed cache key */ private generateCacheKey(deviceId: string): string { return crypto.createHash("md5").update(deviceId).digest("hex"); } /** * Get disk cache file path * @param cacheKey - Cache key for the device * @returns Full path to cache file */ private getCacheFilePath(cacheKey: string): string { return path.join(GetScreenSize.cacheDir, `${cacheKey}.json`); } /** * Load screen size from disk cache * @param cacheKey - Cache key for the device * @returns Screen size if found, null otherwise */ private loadFromDiskCache(cacheKey: string): ScreenSize | null { try { const cacheFile = this.getCacheFilePath(cacheKey); if (fs.existsSync(cacheFile)) { const data = fs.readFileSync(cacheFile, "utf8"); const cached = JSON.parse(data); logger.debug(`Screen size loaded from disk cache for key: ${cacheKey}`); return cached; } } catch (err) { logger.warn(`Failed to load screen size from disk cache: ${err instanceof Error ? err.message : String(err)}`); } return null; } /** * Save screen size to disk cache * @param cacheKey - Cache key for the device * @param screenSize - Screen size to cache */ private saveToDiskCache(cacheKey: string, screenSize: ScreenSize): void { try { // Ensure cache directory exists if (!fs.existsSync(GetScreenSize.cacheDir)) { fs.mkdirSync(GetScreenSize.cacheDir, { recursive: true }); } const cacheFile = this.getCacheFilePath(cacheKey); fs.writeFileSync(cacheFile, JSON.stringify(screenSize, null, 2)); logger.debug(`Screen size saved to disk cache for key: ${cacheKey}`); } catch (err) { logger.warn(`Failed to save screen size to disk cache: ${err instanceof Error ? err.message : String(err)}`); } } /** * Parse physical screen dimensions from dumpsys output * @param stdout - dumpsys output containing size information * @returns Physical width and height */ public parsePhysicalDimensions(stdout: string): { width: number; height: number } { const physicalMatch = stdout.match(/Physical size: (\d+)x(\d+)/); if (!physicalMatch) { throw new Error("Failed to get screen size"); } return { width: parseInt(physicalMatch[1], 10), height: parseInt(physicalMatch[2], 10) }; } /** * Detect device rotation * @returns Promise with rotation value (0-3) */ public async detectDeviceRotation(dumpsysResult: ExecResult): Promise<number> { const rotationMatch = dumpsysResult.stdout.match(/mRotation=(\d+)|mCurrentRotation=(\d+)/); let rotation = 0; if (rotationMatch) { // Get the rotation value from whichever group matched rotation = parseInt(rotationMatch[1] || rotationMatch[2], 10); } logger.debug(`Device rotation detected: ${rotation}`); return rotation; } /** * Adjust dimensions based on rotation * @param width - Physical width * @param height - Physical height * @param rotation - Device rotation (0-3) * @returns Adjusted screen size */ public adjustDimensionsForRotation(width: number, height: number, rotation: number): ScreenSize { // Adjust dimensions based on rotation // 0 = portrait, 1 = landscape (90° clockwise), 2 = portrait upside down, 3 = landscape (270° clockwise) if (rotation === 1 || rotation === 3) { // In landscape mode, swap width and height return { width: height, height: width }; } // In portrait mode, use original dimensions return { width, height }; } /** * Get screen size for Android devices * @param dumpsysResult - Optional dumpsys result for optimization * @returns Promise with screen size */ private async getAndroidScreenSize(dumpsysResult?: ExecResult): Promise<ScreenSize> { // First get the physical screen size const { stdout } = await this.adb.executeCommand("shell wm size"); const { width: physicalWidth, height: physicalHeight } = this.parsePhysicalDimensions(stdout); // Then check the current rotation to determine actual dimensions let rotation = 0; if (dumpsysResult) { rotation = await this.detectDeviceRotation(dumpsysResult); } else { // Get dumpsys result if not provided const dumpsysOutput = await this.adb.executeCommand("shell dumpsys window"); rotation = await this.detectDeviceRotation(dumpsysOutput); } const screenSize = this.adjustDimensionsForRotation(physicalWidth, physicalHeight, rotation); // Cache the result in both memory and disk const cacheKey = this.generateCacheKey(this.device.deviceId); GetScreenSize.memoryCache.set(cacheKey, screenSize); this.saveToDiskCache(cacheKey, screenSize); logger.debug(`Android screen size computed and cached for device: ${this.device.deviceId}`); return screenSize; } /** * Get the screen size and resolution * @returns Promise with width and height */ async execute(dumpsysResult?: ExecResult): Promise<ScreenSize> { const cacheKey = this.generateCacheKey(this.device.deviceId); // Check memory cache first if (GetScreenSize.memoryCache.has(cacheKey)) { logger.debug(`Screen size retrieved from memory cache for device: ${this.device.deviceId}`); return GetScreenSize.memoryCache.get(cacheKey)!; } // Check disk cache const diskCached = this.loadFromDiskCache(cacheKey); if (diskCached) { // Store in memory cache for faster access next time GetScreenSize.memoryCache.set(cacheKey, diskCached); return diskCached; } // Execute actual command if not cached try { const isiOSDevice = DeviceDetection.isiOSDevice(this.device.deviceId); if (isiOSDevice) { // iOS device - use axe to get screen size return await this.axe.getScreenSize(); } else { // Android device - use adb to get screen size return await this.getAndroidScreenSize(dumpsysResult); } } catch (err) { throw new Error(`Failed to get screen size: ${err instanceof Error ? err.message : String(err)}`); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zillow/auto-mobile'

If you have feedback or need assistance with the MCP directory API, please join our Discord server