Initial commit
This commit is contained in:
323
win/rfb_win32/DeviceFrameBuffer.cxx
Normal file
323
win/rfb_win32/DeviceFrameBuffer.cxx
Normal file
@@ -0,0 +1,323 @@
|
||||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
|
||||
* Copyright 2014-2017 Pierre Ossman for Cendio AB
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
||||
* USA.
|
||||
*/
|
||||
|
||||
// -=- DeviceFrameBuffer.cxx
|
||||
//
|
||||
// The DeviceFrameBuffer class encapsulates the pixel data of the system
|
||||
// display.
|
||||
|
||||
#include <vector>
|
||||
#include <rfb_win32/DeviceFrameBuffer.h>
|
||||
#include <rfb_win32/DeviceContext.h>
|
||||
#include <rfb_win32/IconInfo.h>
|
||||
#include <rfb/VNCServer.h>
|
||||
#include <rfb/LogWriter.h>
|
||||
|
||||
using namespace rfb;
|
||||
using namespace win32;
|
||||
|
||||
static LogWriter vlog("DeviceFrameBuffer");
|
||||
|
||||
BoolParameter DeviceFrameBuffer::useCaptureBlt("UseCaptureBlt",
|
||||
"Use a slower capture method that ensures that alpha blended windows appear correctly",
|
||||
true);
|
||||
|
||||
|
||||
// -=- DeviceFrameBuffer class
|
||||
|
||||
DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect)
|
||||
: DIBSectionBuffer(deviceContext), device(deviceContext),
|
||||
ignoreGrabErrors(false)
|
||||
{
|
||||
|
||||
// -=- Firstly, let's check that the device has suitable capabilities
|
||||
|
||||
int capabilities = GetDeviceCaps(device, RASTERCAPS);
|
||||
if (!(capabilities & RC_BITBLT)) {
|
||||
throw Exception("device does not support BitBlt");
|
||||
}
|
||||
if (!(capabilities & RC_DI_BITMAP)) {
|
||||
throw Exception("device does not support GetDIBits");
|
||||
}
|
||||
/*
|
||||
if (GetDeviceCaps(device, PLANES) != 1) {
|
||||
throw Exception("device does not support planar displays");
|
||||
}
|
||||
*/
|
||||
|
||||
// -=- Get the display dimensions and pixel format
|
||||
|
||||
// Get the display dimensions
|
||||
deviceCoords = DeviceContext::getClipBox(device);
|
||||
if (!wRect.is_empty())
|
||||
deviceCoords = wRect.translate(deviceCoords.tl);
|
||||
int w = deviceCoords.width();
|
||||
int h = deviceCoords.height();
|
||||
|
||||
// We can't handle uneven widths :(
|
||||
if (w % 2) w--;
|
||||
|
||||
// Configure the underlying DIB to match the device
|
||||
DIBSectionBuffer::setPF(DeviceContext::getPF(device));
|
||||
DIBSectionBuffer::setSize(w, h);
|
||||
}
|
||||
|
||||
DeviceFrameBuffer::~DeviceFrameBuffer() {
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DeviceFrameBuffer::setPF(const PixelFormat &pf) {
|
||||
throw Exception("setPF not supported");
|
||||
}
|
||||
|
||||
void
|
||||
DeviceFrameBuffer::setSize(int w, int h) {
|
||||
throw Exception("setSize not supported");
|
||||
}
|
||||
|
||||
|
||||
#ifndef CAPTUREBLT
|
||||
#define CAPTUREBLT 0x40000000
|
||||
#endif
|
||||
|
||||
void
|
||||
DeviceFrameBuffer::grabRect(const Rect &rect) {
|
||||
BitmapDC tmpDC(device, bitmap);
|
||||
|
||||
// Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0)
|
||||
Point src = desktopToDevice(rect.tl);
|
||||
|
||||
if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y,
|
||||
rect.width(), rect.height(), device, src.x, src.y,
|
||||
useCaptureBlt ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
|
||||
if (ignoreGrabErrors)
|
||||
vlog.error("BitBlt failed:%ld", GetLastError());
|
||||
else
|
||||
throw rdr::SystemException("BitBlt failed", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DeviceFrameBuffer::grabRegion(const Region &rgn) {
|
||||
std::vector<Rect> rects;
|
||||
std::vector<Rect>::const_iterator i;
|
||||
rgn.get_rects(&rects);
|
||||
for(i=rects.begin(); i!=rects.end(); i++) {
|
||||
grabRect(*i);
|
||||
}
|
||||
::GdiFlush();
|
||||
}
|
||||
|
||||
|
||||
void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
|
||||
{
|
||||
// - If hCursor is null then there is no cursor - clear the old one
|
||||
|
||||
if (hCursor == 0) {
|
||||
server->setCursor(0, 0, Point(), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
int width, height;
|
||||
rdr::U8Array buffer;
|
||||
|
||||
// - Get the size and other details about the cursor.
|
||||
|
||||
IconInfo iconInfo((HICON)hCursor);
|
||||
|
||||
BITMAP maskInfo;
|
||||
if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
|
||||
throw rdr::SystemException("GetObject() failed", GetLastError());
|
||||
if (maskInfo.bmPlanes != 1)
|
||||
throw rdr::Exception("unsupported multi-plane cursor");
|
||||
if (maskInfo.bmBitsPixel != 1)
|
||||
throw rdr::Exception("unsupported cursor mask format");
|
||||
|
||||
width = maskInfo.bmWidth;
|
||||
height = maskInfo.bmHeight;
|
||||
if (!iconInfo.hbmColor)
|
||||
height /= 2;
|
||||
|
||||
buffer.buf = new rdr::U8[width * height * 4];
|
||||
|
||||
Point hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
|
||||
|
||||
if (iconInfo.hbmColor) {
|
||||
// Colour cursor
|
||||
|
||||
BITMAPV5HEADER bi;
|
||||
BitmapDC dc(device, iconInfo.hbmColor);
|
||||
|
||||
memset(&bi, 0, sizeof(BITMAPV5HEADER));
|
||||
|
||||
bi.bV5Size = sizeof(BITMAPV5HEADER);
|
||||
bi.bV5Width = width;
|
||||
bi.bV5Height = -height; // Negative for top-down
|
||||
bi.bV5Planes = 1;
|
||||
bi.bV5BitCount = 32;
|
||||
bi.bV5Compression = BI_BITFIELDS;
|
||||
bi.bV5RedMask = 0x000000FF;
|
||||
bi.bV5GreenMask = 0x0000FF00;
|
||||
bi.bV5BlueMask = 0x00FF0000;
|
||||
bi.bV5AlphaMask = 0xFF000000;
|
||||
|
||||
if (!GetDIBits(dc, iconInfo.hbmColor, 0, height,
|
||||
buffer.buf, (LPBITMAPINFO)&bi, DIB_RGB_COLORS))
|
||||
throw rdr::SystemException("GetDIBits", GetLastError());
|
||||
|
||||
// We may not get the RGBA order we want, so shuffle things around
|
||||
int ridx, gidx, bidx, aidx;
|
||||
|
||||
ridx = __builtin_ffs(bi.bV5RedMask) / 8;
|
||||
gidx = __builtin_ffs(bi.bV5GreenMask) / 8;
|
||||
bidx = __builtin_ffs(bi.bV5BlueMask) / 8;
|
||||
// Usually not set properly
|
||||
aidx = 6 - ridx - gidx - bidx;
|
||||
|
||||
if ((bi.bV5RedMask != ((unsigned)0xff << ridx*8)) ||
|
||||
(bi.bV5GreenMask != ((unsigned)0xff << gidx*8)) ||
|
||||
(bi.bV5BlueMask != ((unsigned)0xff << bidx*8)))
|
||||
throw rdr::Exception("unsupported cursor colour format");
|
||||
|
||||
rdr::U8* rwbuffer = buffer.buf;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
rdr::U8 r, g, b, a;
|
||||
|
||||
r = rwbuffer[ridx];
|
||||
g = rwbuffer[gidx];
|
||||
b = rwbuffer[bidx];
|
||||
a = rwbuffer[aidx];
|
||||
|
||||
rwbuffer[0] = r;
|
||||
rwbuffer[1] = g;
|
||||
rwbuffer[2] = b;
|
||||
rwbuffer[3] = a;
|
||||
|
||||
rwbuffer += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// B/W cursor
|
||||
|
||||
rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
|
||||
rdr::U8* andMask = mask.buf;
|
||||
rdr::U8* xorMask = mask.buf + height * maskInfo.bmWidthBytes;
|
||||
|
||||
if (!GetBitmapBits(iconInfo.hbmMask,
|
||||
maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
|
||||
throw rdr::SystemException("GetBitmapBits", GetLastError());
|
||||
|
||||
bool doOutline = false;
|
||||
rdr::U8* rwbuffer = buffer.buf;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int byte = y * maskInfo.bmWidthBytes + x / 8;
|
||||
int bit = 7 - x % 8;
|
||||
|
||||
if (!(andMask[byte] & (1 << bit))) {
|
||||
// Valid pixel, so make it opaque
|
||||
rwbuffer[3] = 0xff;
|
||||
|
||||
// Black or white?
|
||||
if (xorMask[byte] & (1 << bit))
|
||||
rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0xff;
|
||||
else
|
||||
rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
|
||||
} else if (xorMask[byte] & (1 << bit)) {
|
||||
// Replace any XORed pixels with black, because RFB doesn't support
|
||||
// XORing of cursors. XORing is used for the I-beam cursor, which is most
|
||||
// often used over a white background, but also sometimes over a black
|
||||
// background. We set the XOR'd pixels to black, then draw a white outline
|
||||
// around the whole cursor.
|
||||
|
||||
rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
|
||||
rwbuffer[3] = 0xff;
|
||||
|
||||
doOutline = true;
|
||||
} else {
|
||||
// Transparent pixel
|
||||
rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = rwbuffer[3] = 0;
|
||||
}
|
||||
|
||||
rwbuffer += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (doOutline) {
|
||||
vlog.debug("drawing cursor outline!");
|
||||
|
||||
// The buffer needs to be slightly larger to make sure there
|
||||
// is room for the outline pixels
|
||||
rdr::U8Array outline((width + 2)*(height + 2)*4);
|
||||
memset(outline.buf, 0, (width + 2)*(height + 2)*4);
|
||||
|
||||
// Pass 1, outline everything
|
||||
rdr::U8* in = buffer.buf;
|
||||
rdr::U8* out = outline.buf + width*4 + 4;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// Visible pixel?
|
||||
if (in[3] > 0) {
|
||||
// Outline above...
|
||||
memset(out - (width+2)*4 - 4, 0xff, 4 * 3);
|
||||
// ...besides...
|
||||
memset(out - 4, 0xff, 4 * 3);
|
||||
// ...and above
|
||||
memset(out + (width+2)*4 - 4, 0xff, 4 * 3);
|
||||
}
|
||||
in += 4;
|
||||
out += 4;
|
||||
}
|
||||
// outline is slightly larger
|
||||
out += 2*4;
|
||||
}
|
||||
|
||||
// Pass 2, overwrite with actual cursor
|
||||
in = buffer.buf;
|
||||
out = outline.buf + width*4 + 4;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (in[3] > 0)
|
||||
memcpy(out, in, 4);
|
||||
in += 4;
|
||||
out += 4;
|
||||
}
|
||||
out += 2*4;
|
||||
}
|
||||
|
||||
width += 2;
|
||||
height += 2;
|
||||
hotspot.x += 1;
|
||||
hotspot.y += 1;
|
||||
|
||||
delete [] buffer.buf;
|
||||
buffer.buf = outline.takeBuf();
|
||||
}
|
||||
}
|
||||
|
||||
server->setCursor(width, height, hotspot, buffer.buf);
|
||||
|
||||
} catch (rdr::Exception& e) {
|
||||
vlog.error("%s", e.str());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user