Files
Alvin/src/windows/win32_windows.cpp
T
Aeth3randClaude Opus 5.5 ca7e62ac8d Add Alvin library: input, capture, windows and vision
A cross-platform C++23 library with a platform-neutral public API and
runtime-selected backends:

- Input: Wayland virtual pointer/keyboard (Hyprland, wlroots), libei via
  the RemoteDesktop portal (GNOME, KDE), uinput, and Win32 SendInput
- Capture: ScreenCast portal + PipeWire, and Win32 GDI
- Windows: Hyprland IPC and Win32 enumeration
- Monitors via wl_output/xdg-output and EnumDisplayMonitors
- Optional OpenCV template matching (alvin::vision)

Also adds the `alvin` CLI, Catch2 unit tests, install rules with
find_package(Alvin) support, API docs (docs/API.md and the Obsidian
vault), and README instructions for both ways of consuming the library.

Windows backends are written but not yet compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:58:20 -04:00

119 lines
4.1 KiB
C++

// Top-level window enumeration and focus via Win32 + DWM.
#include <dwmapi.h>
#include "backends.hpp"
#include "win_util.hpp"
namespace alvin::win_impl {
namespace {
std::string processName(DWORD pid) {
HANDLE proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!proc) return {};
wchar_t path[MAX_PATH];
DWORD size = MAX_PATH;
std::string name;
if (QueryFullProcessImageNameW(proc, 0, path, &size)) {
std::wstring_view p(path, size);
if (auto slash = p.find_last_of(L"\\/"); slash != std::wstring_view::npos) p.remove_prefix(slash + 1);
name = narrow(p);
}
CloseHandle(proc);
return name;
}
// Windows a user would consider "open windows" (roughly what Alt+Tab shows).
bool isUserWindow(HWND hwnd) {
if (!IsWindowVisible(hwnd) || GetWindow(hwnd, GW_OWNER) != nullptr) return false;
if (GetWindowLongPtrW(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) return false;
DWORD cloaked = 0; // hidden UWP frames and windows on other virtual desktops
if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof cloaked)) && cloaked) return false;
return GetWindowTextLengthW(hwnd) > 0;
}
WindowInfo describe(HWND hwnd, HWND foreground) {
WindowInfo w;
w.id.value = reinterpret_cast<std::uint64_t>(hwnd);
int len = GetWindowTextLengthW(hwnd);
std::wstring title(static_cast<std::size_t>(len) + 1, L'\0');
GetWindowTextW(hwnd, title.data(), len + 1);
title.resize(static_cast<std::size_t>(len));
w.title = narrow(title);
DWORD pid = 0;
GetWindowThreadProcessId(hwnd, &pid);
w.pid = pid;
w.appId = processName(pid);
// Extended frame bounds exclude the invisible resize borders of Windows 10+.
RECT r{};
if (FAILED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof r))) GetWindowRect(hwnd, &r);
w.bounds = {r.left, r.top, r.right - r.left, r.bottom - r.top};
w.focused = hwnd == foreground;
w.minimized = IsIconic(hwnd) != 0;
w.visible = !w.minimized;
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
MONITORINFO mi{};
mi.cbSize = sizeof mi;
if (mon && GetMonitorInfoW(mon, &mi)) {
const RECT& m = mi.rcMonitor;
w.fullscreen = r.left <= m.left && r.top <= m.top && r.right >= m.right && r.bottom >= m.bottom;
}
return w;
}
class Win32Windows final : public WindowManager {
public:
WindowBackend backend() const override { return WindowBackend::Win32; }
Result<std::vector<WindowInfo>> list() override {
struct Ctx {
HWND foreground;
std::vector<WindowInfo> out;
} ctx{GetForegroundWindow(), {}};
EnumWindows(
[](HWND hwnd, LPARAM data) -> BOOL {
auto* c = reinterpret_cast<Ctx*>(data);
if (isUserWindow(hwnd)) c->out.push_back(describe(hwnd, c->foreground));
return TRUE;
},
reinterpret_cast<LPARAM>(&ctx));
return std::move(ctx.out);
}
Result<std::optional<WindowInfo>> active() override {
HWND fg = GetForegroundWindow();
if (!fg) return std::optional<WindowInfo>{};
return std::optional{describe(fg, fg)};
}
Status focus(WindowId id) override {
HWND hwnd = reinterpret_cast<HWND>(id.value);
if (!IsWindow(hwnd)) return fail(ErrorCode::NotFound, "no such window");
if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE);
if (SetForegroundWindow(hwnd)) return {};
// The foreground lock only lets the process that received the last input
// change focus. A synthetic Alt tap makes us that process.
INPUT alt[2]{};
alt[0].type = alt[1].type = INPUT_KEYBOARD;
alt[0].ki.wVk = alt[1].ki.wVk = VK_MENU;
alt[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, alt, sizeof(INPUT));
if (SetForegroundWindow(hwnd)) return {};
return fail(ErrorCode::PermissionDenied, "Windows refused to change the foreground window");
}
};
} // namespace
Result<std::unique_ptr<WindowManager>> createWin32Windows() {
ensureDpiAware();
return std::make_unique<Win32Windows>();
}
} // namespace alvin::win_impl