blob: 22841f7e1039948becc900185bd768820a54c5fe [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +01002 * Copyright 2014 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)
45 : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext),
46 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);
79
80 // Configure the cursor buffer
81 cursorBm.setPF(format);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000082}
83
84DeviceFrameBuffer::~DeviceFrameBuffer() {
85}
86
87
88void
89DeviceFrameBuffer::setPF(const PixelFormat &pf) {
90 throw Exception("setPF not supported");
91}
92
93void
94DeviceFrameBuffer::setSize(int w, int h) {
95 throw Exception("setSize not supported");
96}
97
98
99#ifndef CAPTUREBLT
100#define CAPTUREBLT 0x40000000
101#endif
102
103void
104DeviceFrameBuffer::grabRect(const Rect &rect) {
105 BitmapDC tmpDC(device, bitmap);
106
107 // Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0)
108 Point src = desktopToDevice(rect.tl);
109
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100110 if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y,
111 rect.width(), rect.height(), device, src.x, src.y,
112 useCaptureBlt ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000113 if (ignoreGrabErrors)
114 vlog.error("BitBlt failed:%ld", GetLastError());
115 else
116 throw rdr::SystemException("BitBlt failed", GetLastError());
117 }
118}
119
120void
121DeviceFrameBuffer::grabRegion(const Region &rgn) {
122 std::vector<Rect> rects;
123 std::vector<Rect>::const_iterator i;
124 rgn.get_rects(&rects);
125 for(i=rects.begin(); i!=rects.end(); i++) {
126 grabRect(*i);
127 }
128 ::GdiFlush();
129}
130
131
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000132void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
133{
134 // - If hCursor is null then there is no cursor - clear the old one
135
136 if (hCursor == 0) {
137 server->setCursor(0, 0, Point(), 0, 0);
138 return;
139 }
140
141 try {
142
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100143 const rdr::U8* buffer;
144 rdr::U8* rwbuffer;
145 int stride;
146
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000147 // - Get the size and other details about the cursor.
148
149 IconInfo iconInfo((HICON)hCursor);
150
151 BITMAP maskInfo;
152 if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
153 throw rdr::SystemException("GetObject() failed", GetLastError());
154 if (maskInfo.bmPlanes != 1)
155 throw rdr::Exception("unsupported multi-plane cursor");
156 if (maskInfo.bmBitsPixel != 1)
157 throw rdr::Exception("unsupported cursor mask format");
158
159 // - Create the cursor pixel buffer and mask storage
160 // NB: The cursor pixel buffer is NOT used here. Instead, we
161 // pass the cursorBm.data pointer directly, to save overhead.
162
163 cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight);
164 cursor.setPF(format);
165 cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
166
167 // - Get the AND and XOR masks. There is only an XOR mask if this is not a
168 // colour cursor.
169
170 if (!iconInfo.hbmColor)
171 cursor.setSize(cursor.width(), cursor.height() / 2);
172 rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
173 rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes;
174
175 if (!GetBitmapBits(iconInfo.hbmMask,
176 maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
177 throw rdr::SystemException("GetBitmapBits failed", GetLastError());
178
179 // Configure the cursor bitmap
180 cursorBm.setSize(cursor.width(), cursor.height());
181
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000182 // Draw the cursor into the bitmap
183 BitmapDC dc(device, cursorBm.bitmap);
184 if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT))
185 throw rdr::SystemException("unable to render cursor", GetLastError());
186
187 // Replace any XORed pixels with xorColour, because RFB doesn't support
188 // XORing of cursors. XORing is used for the I-beam cursor, which is most
189 // often used over a white background, but also sometimes over a black
190 // background. We set the XOR'd pixels to black, then draw a white outline
191 // around the whole cursor.
192
193 // *** should we replace any pixels not set in mask to zero, to ensure
194 // that irrelevant data doesn't screw compression?
195
196 bool doOutline = false;
197 if (!iconInfo.hbmColor) {
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100198 rwbuffer = cursorBm.getBufferRW(cursorBm.getRect(), &stride);
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100199 Pixel xorColour = format.pixelFromRGB((rdr::U16)0, (rdr::U16)0, (rdr::U16)0);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000200 for (int y = 0; y < cursor.height(); y++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000201 for (int x = 0; x < cursor.width(); x++) {
202 int byte = y * maskInfo.bmWidthBytes + x / 8;
203 int bit = 7 - x % 8;
204 if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit)))
205 {
206 mask.buf[byte] &= ~(1 << bit);
207
208 switch (format.bpp) {
209 case 8:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100210 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000211 case 16:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100212 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000213 case 32:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100214 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000215 }
216
217 doOutline = true;
218 }
219 }
220 }
Pierre Ossmana32040d2014-02-06 16:31:10 +0100221 cursorBm.commitBufferRW(cursorBm.getRect());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000222 }
223
224 // Finally invert the AND mask so it's suitable for RFB and pack it into
225 // the minimum number of bytes per row.
226
227 int maskBytesPerRow = (cursor.width() + 7) / 8;
228
229 for (int j = 0; j < cursor.height(); j++) {
230 for (int i = 0; i < maskBytesPerRow; i++)
231 cursor.mask.buf[j * maskBytesPerRow + i]
232 = ~mask.buf[j * maskInfo.bmWidthBytes + i];
233 }
234
235 if (doOutline) {
236 vlog.debug("drawing cursor outline!");
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100237
238 buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
239 cursor.imageRect(cursorBm.getRect(), buffer, stride);
240
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100241 cursor.drawOutline(format.pixelFromRGB((rdr::U16)0xffff, (rdr::U16)0xffff, (rdr::U16)0xffff));
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100242
243 buffer = cursor.getBuffer(cursor.getRect(), &stride);
244 cursorBm.imageRect(cursor.getRect(), buffer, stride);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000245 }
246
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100247 buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000248 server->setCursor(cursor.width(), cursor.height(), cursor.hotspot,
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100249 buffer, cursor.mask.buf);
250
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000251 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000252 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000253 }
254}