blob: 33b1be5ac36f4aec49a7fbfc2a59a2c8ccfcbbd9 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- DeviceFrameBuffer.cxx
20//
21// The DeviceFrameBuffer class encapsulates the pixel data of the system
22// display.
23
24#include <vector>
25#include <rfb_win32/DeviceFrameBuffer.h>
26#include <rfb_win32/DeviceContext.h>
27#include <rfb_win32/OSVersion.h>
28#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
110 // Note: Microsoft's documentation lies directly about CAPTUREBLT and claims it works on 98/ME
111 // If you try CAPTUREBLT on 98 then you get blank output...
112 if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y, rect.width(), rect.height(), device, src.x, src.y,
113 (osVersion.isPlatformNT && useCaptureBlt) ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
114 if (ignoreGrabErrors)
115 vlog.error("BitBlt failed:%ld", GetLastError());
116 else
117 throw rdr::SystemException("BitBlt failed", GetLastError());
118 }
119}
120
121void
122DeviceFrameBuffer::grabRegion(const Region &rgn) {
123 std::vector<Rect> rects;
124 std::vector<Rect>::const_iterator i;
125 rgn.get_rects(&rects);
126 for(i=rects.begin(); i!=rects.end(); i++) {
127 grabRect(*i);
128 }
129 ::GdiFlush();
130}
131
132
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000133void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
134{
135 // - If hCursor is null then there is no cursor - clear the old one
136
137 if (hCursor == 0) {
138 server->setCursor(0, 0, Point(), 0, 0);
139 return;
140 }
141
142 try {
143
144 // - Get the size and other details about the cursor.
145
146 IconInfo iconInfo((HICON)hCursor);
147
148 BITMAP maskInfo;
149 if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
150 throw rdr::SystemException("GetObject() failed", GetLastError());
151 if (maskInfo.bmPlanes != 1)
152 throw rdr::Exception("unsupported multi-plane cursor");
153 if (maskInfo.bmBitsPixel != 1)
154 throw rdr::Exception("unsupported cursor mask format");
155
156 // - Create the cursor pixel buffer and mask storage
157 // NB: The cursor pixel buffer is NOT used here. Instead, we
158 // pass the cursorBm.data pointer directly, to save overhead.
159
160 cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight);
161 cursor.setPF(format);
162 cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
163
164 // - Get the AND and XOR masks. There is only an XOR mask if this is not a
165 // colour cursor.
166
167 if (!iconInfo.hbmColor)
168 cursor.setSize(cursor.width(), cursor.height() / 2);
169 rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
170 rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes;
171
172 if (!GetBitmapBits(iconInfo.hbmMask,
173 maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
174 throw rdr::SystemException("GetBitmapBits failed", GetLastError());
175
176 // Configure the cursor bitmap
177 cursorBm.setSize(cursor.width(), cursor.height());
178
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000179 // Draw the cursor into the bitmap
180 BitmapDC dc(device, cursorBm.bitmap);
181 if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT))
182 throw rdr::SystemException("unable to render cursor", GetLastError());
183
184 // Replace any XORed pixels with xorColour, because RFB doesn't support
185 // XORing of cursors. XORing is used for the I-beam cursor, which is most
186 // often used over a white background, but also sometimes over a black
187 // background. We set the XOR'd pixels to black, then draw a white outline
188 // around the whole cursor.
189
190 // *** should we replace any pixels not set in mask to zero, to ensure
191 // that irrelevant data doesn't screw compression?
192
193 bool doOutline = false;
194 if (!iconInfo.hbmColor) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100195 Pixel xorColour = format.pixelFromRGB((rdr::U16)0, (rdr::U16)0, (rdr::U16)0);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000196 for (int y = 0; y < cursor.height(); y++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000197 for (int x = 0; x < cursor.width(); x++) {
198 int byte = y * maskInfo.bmWidthBytes + x / 8;
199 int bit = 7 - x % 8;
200 if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit)))
201 {
202 mask.buf[byte] &= ~(1 << bit);
203
204 switch (format.bpp) {
205 case 8:
206 ((rdr::U8*)cursorBm.data)[y * cursor.width() + x] = xorColour; break;
207 case 16:
208 ((rdr::U16*)cursorBm.data)[y * cursor.width() + x] = xorColour; break;
209 case 32:
210 ((rdr::U32*)cursorBm.data)[y * cursor.width() + x] = xorColour; break;
211 }
212
213 doOutline = true;
214 }
215 }
216 }
217 }
218
219 // Finally invert the AND mask so it's suitable for RFB and pack it into
220 // the minimum number of bytes per row.
221
222 int maskBytesPerRow = (cursor.width() + 7) / 8;
223
224 for (int j = 0; j < cursor.height(); j++) {
225 for (int i = 0; i < maskBytesPerRow; i++)
226 cursor.mask.buf[j * maskBytesPerRow + i]
227 = ~mask.buf[j * maskInfo.bmWidthBytes + i];
228 }
229
230 if (doOutline) {
231 vlog.debug("drawing cursor outline!");
232 memcpy(cursor.data, cursorBm.data, cursor.dataLen());
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100233 cursor.drawOutline(format.pixelFromRGB((rdr::U16)0xffff, (rdr::U16)0xffff, (rdr::U16)0xffff));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000234 memcpy(cursorBm.data, cursor.data, cursor.dataLen());
235 }
236
237 server->setCursor(cursor.width(), cursor.height(), cursor.hotspot,
238 cursorBm.data, cursor.mask.buf);
239 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000240 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000241 }
242}