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