Files
vatrog-vm-vgpu-streamer/src/stream/win32/cursor.c
T
lirent bcf708d3cc feat: add graphics-context sensors to producer contract
Append three host-readable sensor groups to the producer block, ABI
frozen by offset asserts (producer still fits page 0):

- cursor Tier-1 (hotspot, glyph dims, shape identity), published under
  the existing cursor_seq gate
- content_change_ns: monotonic stamp of the last scene-content change
- display geometry on its own cache line under a geom_seq seqlock:
  virtual-desktop bbox, captured-output origin, DPI, refresh

Source the cursor from each backend's existing grab metadata instead of
polling GetCursorInfo in the present pump: DDA from the duplication frame
info pointer position (no extra calls), NvFBC visibility from the grab
info plus one GetCursorInfo per frame for position, GDI from
GetCursorInfo. Position sampling now rides the frame rate, not the pump
tick.

Sample display geometry once at startup and re-sample only on backend
session recreate or a captured-mode change. Drop per-tick cursor
sampling (cursor_sample_pos) from the present loop.

Add src/stream/win32/geometry.{c,h}.
2026-06-19 01:32:28 +03:00

176 lines
7.1 KiB
C

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>
#include "cursor.h"
#include "vgpu_stream.h" /* VGPU_CURSOR_ID_* */
/* Max supported cursor glyph; buffers are pre-arena'd in ctx (no heap here). */
#define VGPU_CURSOR_MAX 256
static void read_mono(HBITMAP hbm, int w, int h, uint8_t* out /* w*h */) {
int stride = ((w + 31) / 32) * 4;
/* bounded scratch on stack: max (256/32*4)=32 bytes/row * 512 rows */
static const int kMaxRows = VGPU_CURSOR_MAX * 2;
uint8_t raw[(VGPU_CURSOR_MAX / 32 * 4) * (VGPU_CURSOR_MAX * 2)];
if (h > kMaxRows) h = kMaxRows;
if ((size_t)stride * h > sizeof raw) return;
struct { BITMAPINFOHEADER hdr; RGBQUAD pal[2]; } bi;
memset(&bi, 0, sizeof bi);
bi.hdr.biSize = sizeof(BITMAPINFOHEADER);
bi.hdr.biWidth = w; bi.hdr.biHeight = -h;
bi.hdr.biPlanes = 1; bi.hdr.biBitCount = 1; bi.hdr.biCompression = BI_RGB;
HDC dc = GetDC(NULL);
GetDIBits(dc, hbm, 0, h, raw, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
ReleaseDC(NULL, dc);
memset(out, 0, (size_t)w * h);
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
int bit = 7 - (x & 7);
out[(size_t)y * w + x] = (raw[(size_t)y * stride + (x >> 3)] >> bit) & 1u;
}
}
static void extract(vgpu_ctx* ctx, HCURSOR hc) {
vgpu_cursor_t* cur = &ctx->cursor;
cur->gw = cur->gh = 0;
cur->mono = 0;
ICONINFO ii;
if (!GetIconInfo(hc, &ii)) return;
cur->hot_x = (int)ii.xHotspot;
cur->hot_y = (int)ii.yHotspot;
if (ii.hbmColor) {
BITMAP bm; GetObject(ii.hbmColor, sizeof bm, &bm);
int w = bm.bmWidth, h = bm.bmHeight;
if (w > VGPU_CURSOR_MAX) w = VGPU_CURSOR_MAX;
if (h > VGPU_CURSOR_MAX) h = VGPU_CURSOR_MAX;
BITMAPINFO bi; memset(&bi, 0, sizeof bi);
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = -h;
bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
memset(cur->bgra, 0, (size_t)w * h * 4);
HDC dc = GetDC(NULL);
GetDIBits(dc, ii.hbmColor, 0, h, cur->bgra, &bi, DIB_RGB_COLORS);
ReleaseDC(NULL, dc);
cur->gw = w; cur->gh = h; cur->mono = 0;
int has_alpha = 0;
for (size_t i = 0; i < (size_t)w * h; i++)
if (cur->bgra[i * 4 + 3]) { has_alpha = 1; break; }
if (!has_alpha && ii.hbmMask) {
read_mono(ii.hbmMask, w, h, cur->and_mask);
for (size_t i = 0; i < (size_t)w * h; i++)
cur->bgra[i * 4 + 3] = cur->and_mask[i] ? 0 : 255;
}
} else if (ii.hbmMask) {
BITMAP bm; GetObject(ii.hbmMask, sizeof bm, &bm);
int w = bm.bmWidth, h = bm.bmHeight / 2;
if (w > VGPU_CURSOR_MAX) w = VGPU_CURSOR_MAX;
if (h > VGPU_CURSOR_MAX) h = VGPU_CURSOR_MAX;
/* read both halves into a scratch laid over xor_mask region: reuse
* and_mask for AND and xor_mask for XOR; read full into a stack pass */
static uint8_t both[VGPU_CURSOR_MAX * VGPU_CURSOR_MAX * 2];
read_mono(ii.hbmMask, w, bm.bmHeight, both);
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
cur->and_mask[(size_t)y * w + x] = both[(size_t)y * w + x];
cur->xor_mask[(size_t)y * w + x] = both[(size_t)(y + h) * w + x];
}
cur->gw = w; cur->gh = h; cur->mono = 1;
}
if (ii.hbmColor) DeleteObject(ii.hbmColor);
if (ii.hbmMask) DeleteObject(ii.hbmMask);
}
int cursor_resolve_id(HCURSOR hc) {
/* System-cursor table loaded once (IDC_* are stable per session). Lazy: built on first
* call, then a linear handle compare. UNKNOWN for custom/unrecognized cursors. */
static const struct { LPCTSTR idc; int id; } kSpec[] = {
{ IDC_ARROW, VGPU_CURSOR_ID_ARROW },
{ IDC_IBEAM, VGPU_CURSOR_ID_IBEAM },
{ IDC_WAIT, VGPU_CURSOR_ID_WAIT },
{ IDC_CROSS, VGPU_CURSOR_ID_CROSS },
{ IDC_HAND, VGPU_CURSOR_ID_HAND },
{ IDC_SIZENS, VGPU_CURSOR_ID_SIZENS },
{ IDC_SIZEWE, VGPU_CURSOR_ID_SIZEWE },
{ IDC_SIZENWSE, VGPU_CURSOR_ID_SIZENWSE },
{ IDC_SIZENESW, VGPU_CURSOR_ID_SIZENESW },
{ IDC_SIZEALL, VGPU_CURSOR_ID_SIZEALL },
{ IDC_NO, VGPU_CURSOR_ID_NO },
{ IDC_APPSTARTING, VGPU_CURSOR_ID_APPSTARTING },
};
enum { N = (int)(sizeof kSpec / sizeof kSpec[0]) };
static HCURSOR cache[N];
static int loaded = 0;
if (!loaded) {
for (int i = 0; i < N; i++) cache[i] = LoadCursor(NULL, kSpec[i].idc);
loaded = 1;
}
if (!hc) return VGPU_CURSOR_ID_UNKNOWN;
for (int i = 0; i < N; i++)
if (cache[i] == hc) return kSpec[i].id;
return VGPU_CURSOR_ID_UNKNOWN;
}
void cursor_apply_shape(vgpu_ctx* ctx, HCURSOR hc) {
extract(ctx, hc);
ctx->cursor.cursor_id = cursor_resolve_id(hc);
ctx->cursor.handle = hc;
}
int cursor_sample(vgpu_ctx* ctx) {
vgpu_cursor_t* cur = &ctx->cursor;
CURSORINFO ci; ci.cbSize = sizeof ci;
if (!GetCursorInfo(&ci)) {
int changed = cur->visible;
cur->visible = 0;
return changed;
}
int vis = (ci.flags & CURSOR_SHOWING) != 0;
int x = ci.ptScreenPos.x, y = ci.ptScreenPos.y;
int changed = (vis != cur->visible) || (x != cur->x) || (y != cur->y)
|| (ci.hCursor != cur->handle);
if (vis && ci.hCursor && ci.hCursor != cur->handle) {
extract(ctx, ci.hCursor);
cur->cursor_id = cursor_resolve_id(ci.hCursor);
cur->handle = ci.hCursor;
}
cur->visible = vis; cur->x = x; cur->y = y;
return changed;
}
void cursor_draw(vgpu_ctx* ctx, uint8_t* dst, uint32_t W, uint32_t H) {
vgpu_cursor_t* cur = &ctx->cursor;
if (!cur->visible || cur->gw == 0) return;
int ox = cur->x - cur->hot_x, oy = cur->y - cur->hot_y;
for (int gy = 0; gy < cur->gh; gy++) {
int dy = oy + gy;
if (dy < 0 || dy >= (int)H) continue;
for (int gx = 0; gx < cur->gw; gx++) {
int dx = ox + gx;
if (dx < 0 || dx >= (int)W) continue;
uint8_t* d = dst + ((size_t)dy * W + dx) * 4;
if (!cur->mono) {
const uint8_t* s = &cur->bgra[((size_t)gy * cur->gw + gx) * 4];
uint32_t a = s[3];
if (!a) continue;
d[0] = (uint8_t)((s[0] * a + d[0] * (255 - a)) / 255);
d[1] = (uint8_t)((s[1] * a + d[1] * (255 - a)) / 255);
d[2] = (uint8_t)((s[2] * a + d[2] * (255 - a)) / 255);
} else {
int a = cur->and_mask[(size_t)gy * cur->gw + gx];
int xr = cur->xor_mask[(size_t)gy * cur->gw + gx];
if (a == 0 && xr == 0) { d[0] = d[1] = d[2] = 0; }
else if (a == 0 && xr == 1) { d[0] = d[1] = d[2] = 255; }
else if (a == 1 && xr == 1) { d[0] = (uint8_t)(255 - d[0]);
d[1] = (uint8_t)(255 - d[1]);
d[2] = (uint8_t)(255 - d[2]); }
}
}
}
}