# Desktop Target Specification for claude-in-mobile ## Overview Добавление desktop-таргета через Kotlin Multiplatform (Compose Desktop) для автоматизированного тестирования UI приложений на десктопных платформах. ## Целевые платформы | Платформа | Приоритет | Accessibility API | |-----------|-----------|-------------------| | macOS | 1 (первый) | AXUIElement | | Windows | 2 | UI Automation (UIA) | | Linux | 3 | AT-SPI2 | ## Архитектура ### Взаимодействие компонентов ``` ┌─────────────────┐ stdin/stdout ┌──────────────────────┐ │ MCP Server │ ◄──────────────────► │ Desktop App │ │ (Node.js) │ JSON-RPC │ (Compose Desktop) │ └────────┬────────┘ └──────────────────────┘ │ │ set_target('desktop') │ ┌────────▼────────┐ │ ADB Client │ (для Android, параллельно) └─────────────────┘ ``` ### Запуск Desktop-приложения MCP-сервер запускает desktop-app как child process через Gradle: - Команда: `./gradlew :desktopApp:run` или `:composeApp:desktopRun` - Автодетект подходящего таска в проекте - Поддержка кастомных JVM-аргументов и переменных окружения ## MCP Tools API ### Управление таргетами ```typescript // Переключение активного таргета set_target(target: 'desktop' | 'android'): void // Получение текущего таргета get_target(): { target: string, status: 'running' | 'stopped' } ``` ### Запуск приложения ```typescript launch_desktop_app(params: { projectPath: string; // Путь к Gradle-проекту task?: string; // Gradle-таск (автодетект если не указан) jvmArgs?: string[]; // JVM аргументы env?: Record; // Переменные окружения }): { success: boolean, pid: number } ``` ### Input-операции ```typescript // Тап/клик по координатам tap(x: number, y: number): void // Свайп/drag swipe(startX: number, startY: number, endX: number, endY: number, durationMs?: number): void // Ввод текста type_text(text: string): void // Нажатие клавиш key_event(key: string, modifiers?: string[]): void // Примеры: key_event('Enter'), key_event('a', ['ctrl']) ``` ### UI Hierarchy ```typescript get_ui_hierarchy(): { windows: Window[]; } interface Window { id: string; title: string; bounds: Bounds; focused: boolean; elements: UIElement[]; } interface UIElement { // Унифицированный формат (совпадает с Android) id?: string; text?: string; contentDescription?: string; className: string; bounds: Bounds; clickable: boolean; enabled: boolean; focused: boolean; children: UIElement[]; } interface Bounds { x: number; y: number; width: number; height: number; } ``` ### Скриншоты ```typescript screenshot(params?: { windowId?: string; // Конкретное окно (по умолчанию: focused) quality?: number; // JPEG quality 0-100 (по умолчанию: 80) }): { base64: string; // JPEG в base64 width: number; height: number; scaleFactor: number; // HiDPI scale factor } ``` ### Информация об окнах ```typescript get_window_info(): { windows: Array<{ id: string; title: string; bounds: Bounds; focused: boolean; minimized: boolean; fullscreen: boolean; }>; activeWindowId: string; } resize_window(params: { windowId?: string; width: number; height: number; }): void focus_window(windowId: string): void ``` ### Clipboard ```typescript get_clipboard(): { text: string } set_clipboard(text: string): void ``` ### File Dialogs ```typescript // Отслеживание открытия file dialog on_file_dialog(): Promise<{ type: 'open' | 'save', path?: string }> // Автоматический выбор файла в диалоге select_file(path: string): void ``` ### Логи ```typescript get_logs(params?: { type?: 'stdout' | 'stderr' | 'compose' | 'crash' | 'all'; since?: number; // timestamp limit?: number; }): { logs: Array<{ timestamp: number; type: string; message: string; }>; } get_performance_metrics(): { fps: number; memoryUsageMb: number; cpuPercent: number; } ``` ## Технические детали ### Input Injection - **java.awt.Robot** для синтетического input (клики, клавиатура) - **OS Accessibility API** для получения UI-дерева: - macOS: AXUIElement через JNI/JNA - Windows: UI Automation через JNA - Linux: AT-SPI2 через D-Bus ### Fallback стратегия Если элемент не найден через Accessibility API: 1. Использовать координатный клик 2. Сообщить в ответе что элемент найден по координатам, не по semantics ### HiDPI/Retina поддержка - Автодетект scale factor через `GraphicsEnvironment` - Все координаты в API — логические (1x) - Внутренняя конвертация в физические координаты - Скриншоты возвращают `scaleFactor` для информации ### Permissions (macOS) При отсутствии Accessibility permissions: 1. Показать инструкцию пользователю 2. Автоматически открыть System Preferences > Security & Privacy > Accessibility 3. Вернуть ошибку с подробным описанием ```typescript { error: 'ACCESSIBILITY_PERMISSION_REQUIRED', message: 'Accessibility permission required. Opening System Preferences...', instructions: [ '1. Click the lock to make changes', '2. Enable access for Terminal/IDE', '3. Restart the MCP server' ] } ``` ### Crash Handling При падении desktop-app: 1. Сохранить crash stacktrace 2. Автоматически перезапустить приложение 3. Вернуть информацию о crash через `get_logs(type: 'crash')` ### Multi-window поддержка - Список всех окон процесса через `get_window_info()` - Фокус на конкретное окно через `focus_window()` - Работа с диалогами и popup как с отдельными окнами - `screenshot(windowId)` для скриншота конкретного окна ## Performance Requirements | Операция | Target Latency | |----------|----------------| | screenshot | < 300ms | | get_ui_hierarchy | < 500ms | | tap/click | < 50ms | | type_text | < 100ms | ## Фичи на будущее (v2+) - [ ] Запись видео/GIF действий - [ ] Drag-and-drop эмуляция - [ ] System tray взаимодействие - [ ] Запуск из JAR (без Gradle) - [ ] Native меню поддержка ## Зависимости ### Kotlin/JVM - Compose Desktop 1.5+ - JNA (для OS APIs) - kotlinx.serialization (JSON-RPC) ### Node.js (MCP Server) - Существующий код из claude-in-mobile - child_process для управления desktop-app ## Структура файлов ``` claude-in-mobile/ ├── src/ │ ├── desktop/ │ │ ├── client.ts # Desktop client (аналог adb/client.ts) │ │ ├── accessibility/ │ │ │ ├── macos.ts # AXUIElement bindings │ │ │ ├── windows.ts # UI Automation bindings │ │ │ └── linux.ts # AT-SPI2 bindings │ │ ├── input.ts # Robot-based input │ │ └── gradle.ts # Gradle task detection & launch │ └── tools/ │ └── desktop-tools.ts # MCP tool definitions └── docs/ └── SPEC_DESKTOP.md # This file ``` ## Open Questions 1. **Compose Semantics экспорт**: Насколько полно Compose Desktop экспортирует Semantics в OS Accessibility API? Требуется исследование. 2. **Linux поддержка**: AT-SPI2 может работать нестабильно в некоторых дистрибутивах. Возможно потребуется fallback на Robot-only режим. 3. **Native диалоги**: File dialogs на каждой ОС выглядят по-разному и могут не экспортировать accessibility. Возможно потребуется platform-specific код. --- *Спецификация создана: 2026-01-12* *Версия: 1.0*