// Window controller implementation #include "window_controller.h" #include #include #include namespace mosis::test { // Callback for EnumWindows to find window by partial title match struct FindWindowData { std::string searchTitle; HWND foundHwnd = nullptr; }; static BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) { auto* data = reinterpret_cast(lParam); char title[256] = {0}; GetWindowTextA(hwnd, title, sizeof(title)); // Check if the window title contains our search string if (std::string(title).find(data->searchTitle) != std::string::npos) { data->foundHwnd = hwnd; return FALSE; // Stop enumeration } return TRUE; // Continue enumeration } bool WindowController::FindWindow(const std::string& title) { // Use EnumWindows to find window by partial title match // This allows finding "Mosis Designer - home.rml" when searching for "Mosis Designer" FindWindowData data; data.searchTitle = title; EnumWindows(EnumWindowsCallback, reinterpret_cast(&data)); m_hwnd = data.foundHwnd; if (!m_hwnd) { return false; } m_info.hwnd = m_hwnd; // Get window rect ::GetWindowRect(m_hwnd, &m_info.windowRect); // Get client rect (relative to window) RECT clientRect; ::GetClientRect(m_hwnd, &clientRect); m_info.clientRect = clientRect; // Convert client area origin to screen coordinates POINT clientOrigin = {0, 0}; ::ClientToScreen(m_hwnd, &clientOrigin); m_info.clientX = clientOrigin.x; m_info.clientY = clientOrigin.y; // Calculate client size and DPI scale int clientWidth = clientRect.right - clientRect.left; int clientHeight = clientRect.bottom - clientRect.top; m_info.clientWidth = clientWidth; m_info.clientHeight = clientHeight; m_info.scaleX = static_cast(clientWidth) / PHONE_WIDTH; m_info.scaleY = static_cast(clientHeight) / PHONE_HEIGHT; std::cout << "Found window: " << title << std::endl; std::cout << " Window rect: " << m_info.windowRect.left << "," << m_info.windowRect.top << " - " << m_info.windowRect.right << "," << m_info.windowRect.bottom << std::endl; std::cout << " Client origin: " << m_info.clientX << "," << m_info.clientY << std::endl; std::cout << " Client size: " << clientWidth << "x" << clientHeight << std::endl; std::cout << " DPI scale: " << m_info.scaleX << " x " << m_info.scaleY << std::endl; return true; } bool WindowController::WaitForWindow(const std::string& title, int timeoutMs) { auto startTime = std::chrono::steady_clock::now(); while (true) { if (FindWindow(title)) { return true; } auto elapsed = std::chrono::steady_clock::now() - startTime; if (std::chrono::duration_cast(elapsed).count() >= timeoutMs) { std::cerr << "Timeout waiting for window: " << title << std::endl; return false; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } LPARAM WindowController::PhoneToClientLParam(int phoneX, int phoneY) { // Scale phone coordinates to client coordinates int clientX = static_cast(phoneX * m_info.scaleX); int clientY = static_cast(phoneY * m_info.scaleY); // Clamp to client area bounds clientX = std::max(0, std::min(clientX, static_cast(PHONE_WIDTH * m_info.scaleX) - 1)); clientY = std::max(0, std::min(clientY, static_cast(PHONE_HEIGHT * m_info.scaleY) - 1)); return MAKELPARAM(clientX, clientY); } bool WindowController::SendMouseDown(int phoneX, int phoneY) { if (!m_hwnd) return false; // Convert phone coordinates to client coordinates (using window scale) int clientX = static_cast(phoneX * m_info.scaleX); int clientY = static_cast(phoneY * m_info.scaleY); // Calculate screen coordinates int screenX = m_info.clientX + clientX; int screenY = m_info.clientY + clientY; // Get DPI info for debugging UINT dpi = GetDpiForWindow(m_hwnd); // Ensure window is foreground before clicking SetForegroundWindow(m_hwnd); Sleep(10); // Get screen dimensions for absolute coordinate conversion int screenWidth = GetSystemMetrics(SM_CXSCREEN); int screenHeight = GetSystemMetrics(SM_CYSCREEN); // Convert to normalized absolute coordinates (0-65535) LONG absX = static_cast((screenX * 65535) / screenWidth); LONG absY = static_cast((screenY * 65535) / screenHeight); // Send mouse move then button down INPUT moveInput = {}; moveInput.type = INPUT_MOUSE; moveInput.mi.dx = absX; moveInput.mi.dy = absY; moveInput.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; SendInput(1, &moveInput, sizeof(INPUT)); Sleep(20); INPUT clickInput = {}; clickInput.type = INPUT_MOUSE; clickInput.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; SendInput(1, &clickInput, sizeof(INPUT)); std::cout << "MouseDown at phone(" << phoneX << "," << phoneY << ") -> client(" << clientX << "," << clientY << ") screen(" << screenX << "," << screenY << ") dpi=" << dpi << std::endl; return true; } bool WindowController::SendMouseUp(int phoneX, int phoneY) { if (!m_hwnd) return false; Sleep(10); // Small delay before release INPUT input = {}; input.type = INPUT_MOUSE; input.mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(1, &input, sizeof(INPUT)); Sleep(10); // Small delay after release std::cout << "MouseUp at phone(" << phoneX << "," << phoneY << ")" << std::endl; return true; } bool WindowController::SendMouseMove(int phoneX, int phoneY) { if (!m_hwnd) return false; int clientX = static_cast(phoneX * m_info.scaleX); int clientY = static_cast(phoneY * m_info.scaleY); int screenX = m_info.clientX + clientX; int screenY = m_info.clientY + clientY; SetCursorPos(screenX, screenY); return true; } bool WindowController::SendClick(int phoneX, int phoneY) { if (!m_hwnd) return false; // Save current cursor position POINT oldPos; GetCursorPos(&oldPos); // Ensure window is active SetForegroundWindow(m_hwnd); std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Perform click SendMouseDown(phoneX, phoneY); std::this_thread::sleep_for(std::chrono::milliseconds(50)); SendMouseUp(phoneX, phoneY); // Restore cursor position (optional - comment out if cursor restoration causes issues) // std::this_thread::sleep_for(std::chrono::milliseconds(50)); // SetCursorPos(oldPos.x, oldPos.y); return true; } bool WindowController::SendKey(UINT vkCode) { if (!m_hwnd) return false; // Send key down and up LPARAM lParam = 1; // Repeat count = 1 ::PostMessage(m_hwnd, WM_KEYDOWN, vkCode, lParam); std::this_thread::sleep_for(std::chrono::milliseconds(50)); ::PostMessage(m_hwnd, WM_KEYUP, vkCode, lParam | (1 << 30) | (1 << 31)); return true; } bool WindowController::SendChar(char c) { if (!m_hwnd) return false; ::PostMessage(m_hwnd, WM_CHAR, static_cast(c), 0); return true; } bool WindowController::Activate() { if (!m_hwnd) return false; ::SetForegroundWindow(m_hwnd); return true; } bool WindowController::Close() { if (!m_hwnd) return false; ::PostMessage(m_hwnd, WM_CLOSE, 0, 0); return true; } bool WindowController::ResizeToPhone() { if (!m_hwnd) return false; // Skip resizing - the designer creates the window at the correct size // and resizing causes DPI scaling mismatches between window and framebuffer. // Just move window to top-left for consistent test positioning. RECT currentRect; ::GetWindowRect(m_hwnd, ¤tRect); int currentWidth = currentRect.right - currentRect.left; int currentHeight = currentRect.bottom - currentRect.top; std::cout << "ResizeToPhone: keeping current size " << currentWidth << "x" << currentHeight << ", moving to (0,0)" << std::endl; // Move window to top-left for consistent positioning ::MoveWindow(m_hwnd, 0, 0, currentWidth, currentHeight, TRUE); // Re-acquire window info std::this_thread::sleep_for(std::chrono::milliseconds(100)); return FindWindow("Mosis Designer"); } bool WindowController::SendClickFromBottom(int clientX, int offsetFromBottom) { if (!m_hwnd) return false; // Calculate Y position from bottom of client area int clientY = m_info.clientHeight - offsetFromBottom; // Convert to screen coordinates int screenX = m_info.clientX + clientX; int screenY = m_info.clientY + clientY; // Ensure window is active SetForegroundWindow(m_hwnd); std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Get screen dimensions for absolute coordinate conversion int screenWidth = GetSystemMetrics(SM_CXSCREEN); int screenHeight = GetSystemMetrics(SM_CYSCREEN); // Convert to normalized absolute coordinates (0-65535) LONG absX = static_cast((screenX * 65535) / screenWidth); LONG absY = static_cast((screenY * 65535) / screenHeight); // Use SendInput with absolute move + click INPUT inputs[3] = {}; // Move cursor inputs[0].type = INPUT_MOUSE; inputs[0].mi.dx = absX; inputs[0].mi.dy = absY; inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // Mouse down inputs[1].type = INPUT_MOUSE; inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; // Mouse up inputs[2].type = INPUT_MOUSE; inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(3, inputs, sizeof(INPUT)); std::cout << "ClickFromBottom at client(" << clientX << "," << clientY << ") -> screen(" << screenX << "," << screenY << ")" << std::endl; return true; } } // namespace mosis::test