Files
vatrog-vm-vgpu-streamer/src/stream/win32/cursor.c
T

176 lines
7.1 KiB
C
Raw Normal View History

#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]); }
}
}
}
}