#include #include #include #include #include "alvin/capture.hpp" #include "alvin/error.hpp" #include "alvin/input.hpp" #include "alvin/types.hpp" #include "alvin/window.hpp" #include "platform.hpp" namespace alvin { std::string_view toString(ErrorCode code) noexcept { switch (code) { case ErrorCode::Unknown: return "unknown"; case ErrorCode::Unsupported: return "unsupported"; case ErrorCode::NotAvailable: return "not available"; case ErrorCode::PermissionDenied: return "permission denied"; case ErrorCode::Cancelled: return "cancelled"; case ErrorCode::Timeout: return "timeout"; case ErrorCode::InvalidArgument: return "invalid argument"; case ErrorCode::NotFound: return "not found"; case ErrorCode::Io: return "i/o error"; case ErrorCode::Protocol: return "protocol error"; } return "unknown"; } std::string_view toString(InputBackend backend) noexcept { switch (backend) { case InputBackend::Auto: return "auto"; case InputBackend::WlrVirtual: return "wlr"; case InputBackend::LibEi: return "libei"; case InputBackend::UInput: return "uinput"; case InputBackend::SendInput: return "sendinput"; } return "unknown"; } std::string_view toString(CaptureBackend backend) noexcept { switch (backend) { case CaptureBackend::Auto: return "auto"; case CaptureBackend::Portal: return "portal"; case CaptureBackend::Gdi: return "gdi"; } return "unknown"; } std::string_view toString(WindowBackend backend) noexcept { switch (backend) { case WindowBackend::Auto: return "auto"; case WindowBackend::Hyprland: return "hyprland"; case WindowBackend::Win32: return "win32"; } return "unknown"; } Rect Rect::united(const Rect& o) const { if (empty()) return o; if (o.empty()) return *this; int l = std::min(x, o.x), t = std::min(y, o.y); int r = std::max(right(), o.right()), b = std::max(bottom(), o.bottom()); return {l, t, r - l, b - t}; } Result> listMonitors() { return detail::listMonitors(); } Result desktopBounds() { auto monitors = listMonitors(); if (!monitors) return std::unexpected(monitors.error()); Rect box; for (const auto& m : *monitors) box = box.united(m.bounds); if (box.empty()) return fail(ErrorCode::NotFound, "no monitors reported"); return box; } // ---- Input ---- Result> Input::create(const InputOptions& options) { return detail::createInput(options); } void Input::pause() const { if (eventDelay_.count() > 0) std::this_thread::sleep_for(eventDelay_); } Status Input::click(MouseButton button, int count) { for (int i = 0; i < count; ++i) { if (auto s = buttonDown(button); !s) return s; pause(); if (auto s = buttonUp(button); !s) return s; if (i + 1 < count) pause(); } return {}; } Status Input::clickAt(Point p, MouseButton button, int count) { if (auto s = moveTo(p); !s) return s; pause(); return click(button, count); } Status Input::drag(Point from, Point to, MouseButton button) { if (auto s = moveTo(from); !s) return s; pause(); if (auto s = buttonDown(button); !s) return s; // Intermediate steps so drag thresholds and hover handlers fire. constexpr int steps = 10; for (int i = 1; i <= steps; ++i) { pause(); Point p{from.x + (to.x - from.x) * i / steps, from.y + (to.y - from.y) * i / steps}; if (auto s = moveTo(p); !s) { (void)buttonUp(button); return s; } } pause(); return buttonUp(button); } Status Input::keyPress(Key key) { if (auto s = keyDown(key); !s) return s; pause(); return keyUp(key); } // Presses keys in order and releases them in reverse. Always releases what it // pressed, even after a failure, so no key is left stuck down. static Status pressChord(Input& input, std::span keys, void (*pause)(const Input&)) { std::vector pressed; Status result; for (Key k : keys) { result = input.keyDown(k); if (!result) break; pressed.push_back(k); pause(input); } for (auto it = pressed.rbegin(); it != pressed.rend(); ++it) { auto s = input.keyUp(*it); if (result && !s) result = s; } return result; } Status Input::chord(std::initializer_list keys) { return pressChord(*this, std::span(keys.begin(), keys.size()), [](const Input& in) { in.pause(); }); } Status Input::chord(std::string_view text) { auto keys = parseChord(text); if (!keys) return fail(ErrorCode::InvalidArgument, "cannot parse key chord '" + std::string(text) + "'"); return pressChord(*this, *keys, [](const Input& in) { in.pause(); }); } // ---- Capture / windows ---- Result> Capture::create(const CaptureOptions& options) { return detail::createCapture(options); } Result> WindowManager::create(WindowBackend backend) { return detail::createWindowManager(backend); } static bool containsNoCase(std::string_view haystack, std::string_view needle) { auto it = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); }); return it != haystack.end(); } Result> WindowManager::find(std::string_view needle) { auto windows = list(); if (!windows) return std::unexpected(windows.error()); for (auto& w : *windows) if (containsNoCase(w.title, needle) || containsNoCase(w.appId, needle)) return std::optional{std::move(w)}; return std::optional{}; } } // namespace alvin