blob: d075c259d5e249fafe7cc4ec4ffccc0b845b395e [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>
28#include <rfb_win32/OSVersion.h>
29#include <rfb_win32/IconInfo.h>
30#include <rfb/VNCServer.h>
31#include <rfb/LogWriter.h>
32
33using namespace rfb;
34using namespace win32;
35
36static LogWriter vlog("DeviceFrameBuffer");
37
38BoolParameter DeviceFrameBuffer::useCaptureBlt("UseCaptureBlt",
39 "Use a slower capture method that ensures that alpha blended windows appear correctly",
40 true);
41
42
43// -=- DeviceFrameBuffer class
44
45DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect)
46 : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext),
47 ignoreGrabErrors(false)
48{
49
50 // -=- Firstly, let's check that the device has suitable capabilities
51
52 int capabilities = GetDeviceCaps(device, RASTERCAPS);
53 if (!(capabilities & RC_BITBLT)) {
54 throw Exception("device does not support BitBlt");
55 }
56 if (!(capabilities & RC_DI_BITMAP)) {
57 throw Exception("device does not support GetDIBits");
58 }
59 /*
60 if (GetDeviceCaps(device, PLANES) != 1) {
61 throw Exception("device does not support planar displays");
62 }
63 */
64
65 // -=- Get the display dimensions and pixel format
66
67 // Get the display dimensions
68 deviceCoords = DeviceContext::getClipBox(device);
69 if (!wRect.is_empty())
70 deviceCoords = wRect.translate(deviceCoords.tl);
71 int w = deviceCoords.width();
72 int h = deviceCoords.height();
73
74 // We can't handle uneven widths :(
75 if (w % 2) w--;
76
77 // Configure the underlying DIB to match the device
78 DIBSectionBuffer::setPF(DeviceContext::getPF(device));
79 DIBSectionBuffer::setSize(w, h);
80
81 // Configure the cursor buffer
82 cursorBm.setPF(format);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000083}
84
85DeviceFrameBuffer::~DeviceFrameBuffer() {
86}
87
88
89void
90DeviceFrameBuffer::setPF(const PixelFormat &pf) {
91 throw Exception("setPF not supported");
92}
93
94void
95DeviceFrameBuffer::setSize(int w, int h) {
96 throw Exception("setSize not supported");
97}
98
99
100#ifndef CAPTUREBLT
101#define CAPTUREBLT 0x40000000
102#endif
103
104void
105DeviceFrameBuffer::grabRect(const Rect &rect) {
106 BitmapDC tmpDC(device, bitmap);
107
108 // Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0)
109 Point src = desktopToDevice(rect.tl);
110
111 // Note: Microsoft's documentation lies directly about CAPTUREBLT and claims it works on 98/ME
112 // If you try CAPTUREBLT on 98 then you get blank output...
113 if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y, rect.width(), rect.height(), device, src.x, src.y,
114 (osVersion.isPlatformNT && useCaptureBlt) ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
115 if (ignoreGrabErrors)
116 vlog.error("BitBlt failed:%ld", GetLastError());
117 else
118 throw rdr::SystemException("BitBlt failed", GetLastError());
119 }
120}
121
122void
123DeviceFrameBuffer::grabRegion(const Region &rgn) {
124 std::vector<Rect> rects;
125 std::vector<Rect>::const_iterator i;
126 rgn.get_rects(&rects);
127 for(i=rects.begin(); i!=rects.end(); i++) {
128 grabRect(*i);
129 }
130 ::GdiFlush();
131}
132
133
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000134void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
135{
136 // - If hCursor is null then there is no cursor - clear the old one
137
138 if (hCursor == 0) {
139 server->setCursor(0, 0, Point(), 0, 0);
140 return;
141 }
142
143 try {
144
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100145 const rdr::U8* buffer;
146 rdr::U8* rwbuffer;
147 int stride;
148
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000149 // - Get the size and other details about the cursor.
150
151 IconInfo iconInfo((HICON)hCursor);
152
153 BITMAP maskInfo;
154 if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
155 throw rdr::SystemException("GetObject() failed", GetLastError());
156 if (maskInfo.bmPlanes != 1)
157 throw rdr::Exception("unsupported multi-plane cursor");
158 if (maskInfo.bmBitsPixel != 1)
159 throw rdr::Exception("unsupported cursor mask format");
160
161 // - Create the cursor pixel buffer and mask storage
162 // NB: The cursor pixel buffer is NOT used here. Instead, we
163 // pass the cursorBm.data pointer directly, to save overhead.
164
165 cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight);
166 cursor.setPF(format);
167 cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
168
169 // - Get the AND and XOR masks. There is only an XOR mask if this is not a
170 // colour cursor.
171
172 if (!iconInfo.hbmColor)
173 cursor.setSize(cursor.width(), cursor.height() / 2);
174 rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
175 rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes;
176
177 if (!GetBitmapBits(iconInfo.hbmMask,
178 maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
179 throw rdr::SystemException("GetBitmapBits failed", GetLastError());
180
181 // Configure the cursor bitmap
182 cursorBm.setSize(cursor.width(), cursor.height());
183
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000184 // Draw the cursor into the bitmap
185 BitmapDC dc(device, cursorBm.bitmap);
186 if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT))
187 throw rdr::SystemException("unable to render cursor", GetLastError());
188
189 // Replace any XORed pixels with xorColour, because RFB doesn't support
190 // XORing of cursors. XORing is used for the I-beam cursor, which is most
191 // often used over a white background, but also sometimes over a black
192 // background. We set the XOR'd pixels to black, then draw a white outline
193 // around the whole cursor.
194
195 // *** should we replace any pixels not set in mask to zero, to ensure
196 // that irrelevant data doesn't screw compression?
197
198 bool doOutline = false;
199 if (!iconInfo.hbmColor) {
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100200 rwbuffer = cursorBm.getBufferRW(cursorBm.getRect(), &stride);
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100201 Pixel xorColour = format.pixelFromRGB((rdr::U16)0, (rdr::U16)0, (rdr::U16)0);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000202 for (int y = 0; y < cursor.height(); y++) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000203 for (int x = 0; x < cursor.width(); x++) {
204 int byte = y * maskInfo.bmWidthBytes + x / 8;
205 int bit = 7 - x % 8;
206 if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit)))
207 {
208 mask.buf[byte] &= ~(1 << bit);
209
210 switch (format.bpp) {
211 case 8:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100212 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000213 case 16:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100214 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000215 case 32:
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100216 rwbuffer[y * cursor.width() + x] = xorColour; break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000217 }
218
219 doOutline = true;
220 }
221 }
222 }
Pierre Ossmana32040d2014-02-06 16:31:10 +0100223 cursorBm.commitBufferRW(cursorBm.getRect());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000224 }
225
226 // Finally invert the AND mask so it's suitable for RFB and pack it into
227 // the minimum number of bytes per row.
228
229 int maskBytesPerRow = (cursor.width() + 7) / 8;
230
231 for (int j = 0; j < cursor.height(); j++) {
232 for (int i = 0; i < maskBytesPerRow; i++)
233 cursor.mask.buf[j * maskBytesPerRow + i]
234 = ~mask.buf[j * maskInfo.bmWidthBytes + i];
235 }
236
237 if (doOutline) {
238 vlog.debug("drawing cursor outline!");
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100239
240 buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
241 cursor.imageRect(cursorBm.getRect(), buffer, stride);
242
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100243 cursor.drawOutline(format.pixelFromRGB((rdr::U16)0xffff, (rdr::U16)0xffff, (rdr::U16)0xffff));
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100244
245 buffer = cursor.getBuffer(cursor.getRect(), &stride);
246 cursorBm.imageRect(cursor.getRect(), buffer, stride);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000247 }
248
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100249 buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000250 server->setCursor(cursor.width(), cursor.height(), cursor.hotspot,
Pierre Ossmanff9eb5a2014-01-30 17:47:31 +0100251 buffer, cursor.mask.buf);
252
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000253 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000254 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000255 }
256}