2026-06-17 12:55:19 +03:00
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include "cursor.h"
|
2026-06-19 01:32:28 +03:00
|
|
|
#include "vgpu_stream.h" /* VGPU_CURSOR_ID_* */
|
2026-06-17 12:55:19 +03:00
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 01:32:28 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 12:55:19 +03:00
|
|
|
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);
|
2026-06-19 01:32:28 +03:00
|
|
|
cur->cursor_id = cursor_resolve_id(ci.hCursor);
|
2026-06-17 12:55:19 +03:00
|
|
|
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]); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|