blob: 3138cce2d6bb733f77d27a4cc9a0f01855e6191c [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossman6a1a0d02017-02-19 15:48:17 +01002 * Copyright 2014-2017 Pierre Ossman for Cendio AB
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20// -=- DeviceFrameBuffer.cxx
21//
22// The DeviceFrameBuffer class encapsulates the pixel data of the system
23// display.
24
25#include <vector>
26#include <rfb_win32/DeviceFrameBuffer.h>
27#include <rfb_win32/DeviceContext.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000028#include <rfb_win32/IconInfo.h>
29#include <rfb/VNCServer.h>
30#include <rfb/LogWriter.h>
31
32using namespace rfb;
33using namespace win32;
34
35static LogWriter vlog("DeviceFrameBuffer");
36
37BoolParameter DeviceFrameBuffer::useCaptureBlt("UseCaptureBlt",
38 "Use a slower capture method that ensures that alpha blended windows appear correctly",
39 true);
40
41
42// -=- DeviceFrameBuffer class
43
44DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect)
Pierre Ossman6a1a0d02017-02-19 15:48:17 +010045 : DIBSectionBuffer(deviceContext), device(deviceContext),
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000046 ignoreGrabErrors(false)
47{
48
49 // -=- Firstly, let's check that the device has suitable capabilities
50
51 int capabilities = GetDeviceCaps(device, RASTERCAPS);
52 if (!(capabilities & RC_BITBLT)) {
53 throw Exception("device does not support BitBlt");
54 }
55 if (!(capabilities & RC_DI_BITMAP)) {
56 throw Exception("device does not support GetDIBits");
57 }
58 /*
59 if (GetDeviceCaps(device, PLANES) != 1) {
60 throw Exception("device does not support planar displays");
61 }
62 */
63
64 // -=- Get the display dimensions and pixel format
65
66 // Get the display dimensions
67 deviceCoords = DeviceContext::getClipBox(device);
68 if (!wRect.is_empty())
69 deviceCoords = wRect.translate(deviceCoords.tl);
70 int w = deviceCoords.width();
71 int h = deviceCoords.height();
72
73 // We can't handle uneven widths :(
74 if (w % 2) w--;
75
76 // Configure the underlying DIB to match the device
77 DIBSectionBuffer::setPF(DeviceContext::getPF(device));
78 DIBSectionBuffer::setSize(w, h);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000079}
80
81DeviceFrameBuffer::~DeviceFrameBuffer() {
82}
83
84
85void
86DeviceFrameBuffer::setPF(const PixelFormat &pf) {
87 throw Exception("setPF not supported");
88}
89
90void
91DeviceFrameBuffer::setSize(int w, int h) {
92 throw Exception("setSize not supported");
93}
94
95
96#ifndef CAPTUREBLT
97#define CAPTUREBLT 0x40000000
98#endif
99
100void
101DeviceFrameBuffer::grabRect(const Rect &rect) {
102 BitmapDC tmpDC(device, bitmap);
103
104 // Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0)
105 Point src = desktopToDevice(rect.tl);
106
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100107 if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y,
108 rect.width(), rect.height(), device, src.x, src.y,
109 useCaptureBlt ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000110 if (ignoreGrabErrors)
111 vlog.error("BitBlt failed:%ld", GetLastError());
112 else
113 throw rdr::SystemException("BitBlt failed", GetLastError());
114 }
115}
116
117void
118DeviceFrameBuffer::grabRegion(const Region &rgn) {
119 std::vector<Rect> rects;
120 std::vector<Rect>::const_iterator i;
121 rgn.get_rects(&rects);
122 for(i=rects.begin(); i!=rects.end(); i++) {
123 grabRect(*i);
124 }
125 ::GdiFlush();
126}
127
128
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000129void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
130{
131 // - If hCursor is null then there is no cursor - clear the old one
132
133 if (hCursor == 0) {
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100134 server->setCursor(0, 0, Point(), NULL);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000135 return;
136 }
137
138 try {
139
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100140 int width, height;
141 rdr::U8Array buffer;
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100142
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000143 // - Get the size and other details about the cursor.
144
145 IconInfo iconInfo((HICON)hCursor);
146
147 BITMAP maskInfo;
148 if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
149 throw rdr::SystemException("GetObject() failed", GetLastError());
150 if (maskInfo.bmPlanes != 1)
151 throw rdr::Exception("unsupported multi-plane cursor");
152 if (maskInfo.bmBitsPixel != 1)
153 throw rdr::Exception("unsupported cursor mask format");
154
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100155 width = maskInfo.bmWidth;
156 height = maskInfo.bmHeight;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000157 if (!iconInfo.hbmColor)
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100158 height /= 2;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000159
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100160 buffer.buf = new rdr::U8[width * height * 4];
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000161
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100162 Point hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000163
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100164 if (iconInfo.hbmColor) {
165 // Colour cursor
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000166
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100167 BITMAPV5HEADER bi;
168 BitmapDC dc(device, iconInfo.hbmColor);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000169
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100170 memset(&bi, 0, sizeof(BITMAPV5HEADER));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000171
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100172 bi.bV5Size = sizeof(BITMAPV5HEADER);
173 bi.bV5Width = width;
174 bi.bV5Height = -height; // Negative for top-down
175 bi.bV5Planes = 1;
176 bi.bV5BitCount = 32;
177 bi.bV5Compression = BI_BITFIELDS;
178 bi.bV5RedMask = 0x000000FF;
179 bi.bV5GreenMask = 0x0000FF00;
180 bi.bV5BlueMask = 0x00FF0000;
181 bi.bV5AlphaMask = 0xFF000000;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000182
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100183 if (!GetDIBits(dc, iconInfo.hbmColor, 0, height,
184 buffer.buf, (LPBITMAPINFO)&bi, DIB_RGB_COLORS))
185 throw rdr::SystemException("GetDIBits", GetLastError());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000186
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100187 // We may not get the RGBA order we want, so shuffle things around
188 int ridx, gidx, bidx, aidx;
189
190 ridx = __builtin_ffs(bi.bV5RedMask) / 8;
191 gidx = __builtin_ffs(bi.bV5GreenMask) / 8;
192 bidx = __builtin_ffs(bi.bV5BlueMask) / 8;
193 // Usually not set properly
194 aidx = 6 - ridx - gidx - bidx;
195
196 if ((bi.bV5RedMask != ((unsigned)0xff << ridx*8)) ||
197 (bi.bV5GreenMask != ((unsigned)0xff << gidx*8)) ||
198 (bi.bV5BlueMask != ((unsigned)0xff << bidx*8)))
199 throw rdr::Exception("unsupported cursor colour format");
200
201 rdr::U8* rwbuffer = buffer.buf;
202 for (int y = 0; y < height; y++) {
203 for (int x = 0; x < width; x++) {
204 rdr::U8 r, g, b, a;
205
206 r = rwbuffer[ridx];
207 g = rwbuffer[gidx];
208 b = rwbuffer[bidx];
209 a = rwbuffer[aidx];
210
211 rwbuffer[0] = r;
212 rwbuffer[1] = g;
213 rwbuffer[2] = b;
214 rwbuffer[3] = a;
215
216 rwbuffer += 4;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000217 }
218 }
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100219 } else {
220 // B/W cursor
221
222 rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
223 rdr::U8* andMask = mask.buf;
224 rdr::U8* xorMask = mask.buf + height * maskInfo.bmWidthBytes;
225
226 if (!GetBitmapBits(iconInfo.hbmMask,
227 maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
228 throw rdr::SystemException("GetBitmapBits", GetLastError());
229
230 bool doOutline = false;
231 rdr::U8* rwbuffer = buffer.buf;
232 for (int y = 0; y < height; y++) {
233 for (int x = 0; x < width; x++) {
234 int byte = y * maskInfo.bmWidthBytes + x / 8;
235 int bit = 7 - x % 8;
236
237 if (!(andMask[byte] & (1 << bit))) {
238 // Valid pixel, so make it opaque
239 rwbuffer[3] = 0xff;
240
241 // Black or white?
242 if (xorMask[byte] & (1 << bit))
243 rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0xff;
244 else
245 rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
246 } else if (xorMask[byte] & (1 << bit)) {
247 // Replace any XORed pixels with black, because RFB doesn't support
248 // XORing of cursors. XORing is used for the I-beam cursor, which is most
249 // often used over a white background, but also sometimes over a black
250 // background. We set the XOR'd pixels to black, then draw a white outline
251 // around the whole cursor.
252
253 rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
254 rwbuffer[3] = 0xff;
255
256 doOutline = true;
257 } else {
258 // Transparent pixel
259 rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = rwbuffer[3] = 0;
260 }
261
262 rwbuffer += 4;
263 }
264 }
265
266 if (doOutline) {
267 vlog.debug("drawing cursor outline!");
268
269 // The buffer needs to be slightly larger to make sure there
270 // is room for the outline pixels
271 rdr::U8Array outline((width + 2)*(height + 2)*4);
272 memset(outline.buf, 0, (width + 2)*(height + 2)*4);
273
274 // Pass 1, outline everything
275 rdr::U8* in = buffer.buf;
276 rdr::U8* out = outline.buf + width*4 + 4;
277 for (int y = 0; y < height; y++) {
278 for (int x = 0; x < width; x++) {
279 // Visible pixel?
280 if (in[3] > 0) {
281 // Outline above...
282 memset(out - (width+2)*4 - 4, 0xff, 4 * 3);
283 // ...besides...
284 memset(out - 4, 0xff, 4 * 3);
285 // ...and above
286 memset(out + (width+2)*4 - 4, 0xff, 4 * 3);
287 }
288 in += 4;
289 out += 4;
290 }
291 // outline is slightly larger
292 out += 2*4;
293 }
294
295 // Pass 2, overwrite with actual cursor
296 in = buffer.buf;
297 out = outline.buf + width*4 + 4;
298 for (int y = 0; y < height; y++) {
299 for (int x = 0; x < width; x++) {
300 if (in[3] > 0)
301 memcpy(out, in, 4);
302 in += 4;
303 out += 4;
304 }
305 out += 2*4;
306 }
307
308 width += 2;
309 height += 2;
310 hotspot.x += 1;
311 hotspot.y += 1;
312
313 delete [] buffer.buf;
314 buffer.buf = outline.takeBuf();
315 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000316 }
317
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100318 server->setCursor(width, height, hotspot, buffer.buf);
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100319
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000320 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000321 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000322 }
323}