blob: 744149da0b3f4747e5f66acaacc3ea37372febd4 [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#include <windows.h>
20#include <commctrl.h>
george82770bbbc2007-03-12 10:48:09 +000021#include <math.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000022#include <rfb/Configuration.h>
23#include <rfb/LogWriter.h>
24#include <rfb_win32/WMShatter.h>
25#include <rfb_win32/LowLevelKeyEvents.h>
26#include <rfb_win32/MonitorInfo.h>
27#include <rfb_win32/DeviceContext.h>
28#include <rfb_win32/Win32Util.h>
george82fd334ad2006-05-29 14:05:20 +000029#include <rfb_win32/MsgBox.h>
Peter Åstrand1cd3dda2008-12-09 11:17:28 +000030#include <rfb/util.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000031#include <vncviewer/DesktopWindow.h>
32#include <vncviewer/resource.h>
33
34using namespace rfb;
35using namespace rfb::win32;
36
37
38// - Statics & consts
39
40static LogWriter vlog("DesktopWindow");
41
42const int TIMER_BUMPSCROLL = 1;
43const int TIMER_POINTER_INTERVAL = 2;
44const int TIMER_POINTER_3BUTTON = 3;
Pierre Ossmanb2ff1602009-03-25 12:13:28 +000045const int TIMER_UPDATE = 4;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000046
47
48//
49// -=- DesktopWindowClass
50
51//
52// Window class used as the basis for all DesktopWindow instances
53//
54
55class DesktopWindowClass {
56public:
57 DesktopWindowClass();
58 ~DesktopWindowClass();
59 ATOM classAtom;
60 HINSTANCE instance;
61};
62
63LRESULT CALLBACK DesktopWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
Peter Åstrand9ae9d0f2008-12-11 09:09:07 +000064 LRESULT result = 0;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000065 if (msg == WM_CREATE)
66 SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
67 else if (msg == WM_DESTROY)
68 SetWindowLong(wnd, GWL_USERDATA, 0);
69 DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA);
70 if (!_this) {
71 vlog.info("null _this in %x, message %u", wnd, msg);
72 return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam);
73 }
74
75 try {
76 result = _this->processMessage(msg, wParam, lParam);
george82fd334ad2006-05-29 14:05:20 +000077 } catch (rfb::UnsupportedPixelFormatException &e) {
george823e43ed52007-11-05 17:13:10 +000078 MsgBox(0, e.str(), MB_OK | MB_ICONINFORMATION);
george82fd334ad2006-05-29 14:05:20 +000079 _this->getCallback()->closeWindow();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000080 } catch (rdr::Exception& e) {
81 vlog.error("untrapped: %s", e.str());
82 }
83
84 return result;
85};
86
87static HCURSOR dotCursor = (HCURSOR)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDC_DOT_CURSOR), IMAGE_CURSOR, 0, 0, LR_SHARED);
88static HCURSOR arrowCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
89
90DesktopWindowClass::DesktopWindowClass() : classAtom(0) {
91 WNDCLASS wndClass;
92 wndClass.style = 0;
93 wndClass.lpfnWndProc = DesktopWindowProc;
94 wndClass.cbClsExtra = 0;
95 wndClass.cbWndExtra = 0;
96 wndClass.hInstance = instance = GetModuleHandle(0);
97 wndClass.hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 0, 0, LR_SHARED);
98 if (!wndClass.hIcon)
99 printf("unable to load icon:%ld", GetLastError());
100 wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
101 wndClass.hbrBackground = NULL;
102 wndClass.lpszMenuName = 0;
103 wndClass.lpszClassName = _T("rfb::win32::DesktopWindowClass");
104 classAtom = RegisterClass(&wndClass);
105 if (!classAtom) {
106 throw rdr::SystemException("unable to register DesktopWindow window class", GetLastError());
107 }
108}
109
110DesktopWindowClass::~DesktopWindowClass() {
111 if (classAtom) {
112 UnregisterClass((const TCHAR*)classAtom, instance);
113 }
114}
115
Peter Åstrandf84b0432008-12-09 10:41:59 +0000116static DesktopWindowClass baseClass;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000117
118//
119// -=- FrameClass
120
121//
122// Window class used for child windows that display pixel data
123//
124
125class FrameClass {
126public:
127 FrameClass();
128 ~FrameClass();
129 ATOM classAtom;
130 HINSTANCE instance;
131};
132
133LRESULT CALLBACK FrameProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
Peter Åstrand35a70392008-12-11 09:08:33 +0000134 LRESULT result = 0;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000135 if (msg == WM_CREATE)
136 SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
137 else if (msg == WM_DESTROY)
138 SetWindowLong(wnd, GWL_USERDATA, 0);
139 DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA);
140 if (!_this) {
141 vlog.info("null _this in %x, message %u", wnd, msg);
142 return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam);
143 }
144
145 try {
146 result = _this->processFrameMessage(msg, wParam, lParam);
147 } catch (rdr::Exception& e) {
148 vlog.error("untrapped: %s", e.str());
149 }
150
151 return result;
152}
153
154FrameClass::FrameClass() : classAtom(0) {
155 WNDCLASS wndClass;
156 wndClass.style = 0;
157 wndClass.lpfnWndProc = FrameProc;
158 wndClass.cbClsExtra = 0;
159 wndClass.cbWndExtra = 0;
160 wndClass.hInstance = instance = GetModuleHandle(0);
161 wndClass.hIcon = 0;
162 wndClass.hCursor = NULL;
163 wndClass.hbrBackground = NULL;
164 wndClass.lpszMenuName = 0;
165 wndClass.lpszClassName = _T("rfb::win32::FrameClass");
166 classAtom = RegisterClass(&wndClass);
167 if (!classAtom) {
168 throw rdr::SystemException("unable to register Frame window class", GetLastError());
169 }
170}
171
172FrameClass::~FrameClass() {
173 if (classAtom) {
174 UnregisterClass((const TCHAR*)classAtom, instance);
175 }
176}
177
178FrameClass frameClass;
179
180
181//
182// -=- DesktopWindow instance implementation
183//
184
185DesktopWindow::DesktopWindow(Callback* cb)
Peter Åstrand36a93e52008-12-11 08:49:23 +0000186 : bumpScroll(false), palette_changed(false), fullscreenActive(false),
187 fullscreenRestore(false), systemCursorVisible(true), trackingMouseLeave(false),
188 cursorInBuffer(false), cursorVisible(false), cursorAvailable(false),
189 internalSetCursor(false), cursorImage(0), cursorMask(0),
190 cursorWidth(0), cursorHeight(0), showToolbar(false),
191 buffer(0), has_focus(false), autoScaling(false),
192 window_size(0, 0, 32, 32), client_size(0, 0, 16, 16), handle(0),
193 frameHandle(0), callback(cb) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000194
195 // Create the window
196 const char* name = "DesktopWindow";
197 handle = CreateWindow((const TCHAR*)baseClass.classAtom, TStr(name),
198 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
199 0, 0, 10, 10, 0, 0, baseClass.instance, this);
200 if (!handle)
201 throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError());
202 vlog.debug("created window \"%s\" (%x)", name, handle);
203
204 // Create the toolbar
205 tb.create(handle);
206 vlog.debug("created toolbar window \"%s\" (%x)", "ViewerToolBar", tb.getHandle());
207
208 // Create the frame window
209 frameHandle = CreateWindowEx(WS_EX_CLIENTEDGE, (const TCHAR*)frameClass.classAtom,
210 0, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
211 CW_USEDEFAULT, CW_USEDEFAULT, handle, 0, frameClass.instance, this);
212 if (!frameHandle) {
213 throw rdr::SystemException("unable to create rfb frame window instance", GetLastError());
214 }
215 vlog.debug("created window \"%s\" (%x)", "Frame Window", frameHandle);
216
217 // Initialise the CPointer pointer handler
218 ptr.setHWND(frameHandle);
219 ptr.setIntervalTimerId(TIMER_POINTER_INTERVAL);
220 ptr.set3ButtonTimerId(TIMER_POINTER_3BUTTON);
221
222 // Initialise the bumpscroll timer
223 bumpScrollTimer.setHWND(handle);
224 bumpScrollTimer.setId(TIMER_BUMPSCROLL);
225
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000226 // Initialise the update timer
227 updateTimer.setHWND(handle);
228 updateTimer.setId(TIMER_UPDATE);
229
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000230 // Hook the clipboard
231 clipboard.setNotifier(this);
232
233 // Create the backing buffer
234 buffer = new win32::ScaledDIBSectionBuffer(frameHandle);
235
236 // Show the window
237 centerWindow(handle, 0);
238 ShowWindow(handle, SW_SHOW);
239}
240
241DesktopWindow::~DesktopWindow() {
242 vlog.debug("~DesktopWindow");
243 showSystemCursor();
244 if (handle) {
245 disableLowLevelKeyEvents(handle);
246 DestroyWindow(handle);
247 handle = 0;
248 }
249 delete buffer;
george824880eec2007-03-19 10:55:13 +0000250 if (cursorImage) delete [] cursorImage;
251 if (cursorMask) delete [] cursorMask;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000252 vlog.debug("~DesktopWindow done");
253}
254
255
256void DesktopWindow::setFullscreen(bool fs) {
257 if (fs && !fullscreenActive) {
258 fullscreenActive = bumpScroll = true;
259
260 // Un-minimize the window if required
261 if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE)
262 ShowWindow(handle, SW_RESTORE);
263
264 // Save the current window position
265 GetWindowRect(handle, &fullscreenOldRect);
266
267 // Find the size of the display the window is on
268 MonitorInfo mi(handle);
269
270 // Hide the toolbar
271 if (tb.isVisible())
272 tb.hide();
273 SetWindowLong(frameHandle, GWL_EXSTYLE, 0);
274
275 // Set the window full-screen
276 DWORD flags = GetWindowLong(handle, GWL_STYLE);
277 fullscreenOldFlags = flags;
278 flags = flags & ~(WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZE | WS_MINIMIZE);
279 vlog.debug("flags=%x", flags);
280
281 SetWindowLong(handle, GWL_STYLE, flags);
george82ced9db12006-09-11 09:09:14 +0000282 SetWindowPos(handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000283 mi.rcMonitor.right-mi.rcMonitor.left,
284 mi.rcMonitor.bottom-mi.rcMonitor.top,
285 SWP_FRAMECHANGED);
286 } else if (!fs && fullscreenActive) {
287 fullscreenActive = bumpScroll = false;
288
289 // Show the toolbar
290 if (showToolbar)
291 tb.show();
292 SetWindowLong(frameHandle, GWL_EXSTYLE, WS_EX_CLIENTEDGE);
293
294 // Set the window non-fullscreen
295 SetWindowLong(handle, GWL_STYLE, fullscreenOldFlags);
296
297 // Set the window position
298 SetWindowPos(handle, HWND_NOTOPMOST,
299 fullscreenOldRect.left, fullscreenOldRect.top,
300 fullscreenOldRect.right - fullscreenOldRect.left,
301 fullscreenOldRect.bottom - fullscreenOldRect.top,
302 SWP_FRAMECHANGED);
303 }
304
305 // Adjust the viewport offset to cope with change in size between FS
306 // and previous window state.
307 setViewportOffset(scrolloffset);
308}
309
310void DesktopWindow::setShowToolbar(bool st)
311{
312 showToolbar = st;
george82be3e9692006-06-10 12:58:41 +0000313 if (fullscreenActive) return;
314
george8222856792006-06-10 11:47:22 +0000315 RECT r;
316 GetWindowRect(handle, &r);
317 bool maximized = GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000318
george8222856792006-06-10 11:47:22 +0000319 if (showToolbar && !tb.isVisible()) {
george8274ea5f32006-09-11 11:40:12 +0000320 refreshToolbarButtons();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000321 tb.show();
george8222856792006-06-10 11:47:22 +0000322 if (!maximized) r.bottom += tb.getHeight();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000323 } else if (!showToolbar && tb.isVisible()) {
324 tb.hide();
george8222856792006-06-10 11:47:22 +0000325 if (!maximized) r.bottom -= tb.getHeight();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000326 }
george8222856792006-06-10 11:47:22 +0000327 // Resize the chiled windows even if the parent window size
328 // has not been changed (the main window is maximized)
329 if (maximized) SendMessage(handle, WM_SIZE, 0, 0);
330 else SetWindowPos(handle, NULL, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOMOVE | SWP_NOZORDER);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000331}
332
george8274ea5f32006-09-11 11:40:12 +0000333void DesktopWindow::refreshToolbarButtons() {
334 int scale = getDesktopScale();
george8274ea5f32006-09-11 11:40:12 +0000335 if (scale <= 10) {
336 tb.enableButton(ID_ZOOM_IN, true);
337 tb.enableButton(ID_ZOOM_OUT, false);
338 } else if (scale >= 200) {
339 tb.enableButton(ID_ZOOM_IN, false);
340 tb.enableButton(ID_ZOOM_OUT, true);
341 } else {
342 tb.enableButton(ID_ZOOM_IN, true);
343 tb.enableButton(ID_ZOOM_OUT, true);
344 }
george824a185b02007-03-12 14:26:33 +0000345 if (buffer->isScaling() || isAutoScaling()) tb.enableButton(ID_ACTUAL_SIZE, true);
346 else tb.enableButton(ID_ACTUAL_SIZE, false);
george8274ea5f32006-09-11 11:40:12 +0000347 if (isAutoScaling()) tb.pressButton(ID_AUTO_SIZE, true);
348 else tb.pressButton(ID_AUTO_SIZE, false);
349}
350
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000351void DesktopWindow::setDisableWinKeys(bool dwk) {
352 // Enable low-level event hooking, so we get special keys directly
353 if (dwk)
354 enableLowLevelKeyEvents(handle);
355 else
356 disableLowLevelKeyEvents(handle);
357}
358
359
360void DesktopWindow::setMonitor(const char* monitor) {
361 MonitorInfo mi(monitor);
362 mi.moveTo(handle);
363}
364
365char* DesktopWindow::getMonitor() const {
366 MonitorInfo mi(handle);
367 return strDup(mi.szDevice);
368}
369
370
371bool DesktopWindow::setViewportOffset(const Point& tl) {
Peter Åstrand1cd3dda2008-12-09 11:17:28 +0000372 Point np = Point(__rfbmax(0, __rfbmin(tl.x, buffer->width()-client_size.width())),
373 __rfbmax(0, __rfbmin(tl.y, buffer->height()-client_size.height())));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000374 Point delta = np.translate(scrolloffset.negate());
375 if (!np.equals(scrolloffset)) {
376 scrolloffset = np;
377 ScrollWindowEx(frameHandle, -delta.x, -delta.y, 0, 0, 0, 0, SW_INVALIDATE);
378 UpdateWindow(frameHandle);
379 return true;
380 }
381 return false;
382}
383
384
385bool DesktopWindow::processBumpScroll(const Point& pos)
386{
387 if (!bumpScroll) return false;
388 int bumpScrollPixels = 20;
389 bumpScrollDelta = Point();
390
391 if (pos.x == client_size.width()-1)
392 bumpScrollDelta.x = bumpScrollPixels;
393 else if (pos.x == 0)
394 bumpScrollDelta.x = -bumpScrollPixels;
395 if (pos.y == client_size.height()-1)
396 bumpScrollDelta.y = bumpScrollPixels;
397 else if (pos.y == 0)
398 bumpScrollDelta.y = -bumpScrollPixels;
399
400 if (bumpScrollDelta.x || bumpScrollDelta.y) {
401 if (bumpScrollTimer.isActive()) return true;
402 if (setViewportOffset(scrolloffset.translate(bumpScrollDelta))) {
403 bumpScrollTimer.start(25);
404 return true;
405 }
406 }
407
408 bumpScrollTimer.stop();
409 return false;
410}
411
412
413LRESULT
414DesktopWindow::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
415 switch (msg) {
416
417 // -=- Process standard window messages
418
419 case WM_NOTIFY:
420 if (wParam == ID_TOOLBAR)
421 tb.processWM_NOTIFY(wParam, lParam);
422 break;
423
424 case WM_DISPLAYCHANGE:
425 // Display format has changed - notify callback
426 callback->displayChanged();
427 break;
428
429 // -=- Window position
430
431 // Prevent the window from being resized to be too large if in normal mode.
432 // If maximized or fullscreen the allow oversized windows.
433
434 case WM_WINDOWPOSCHANGING:
435 {
436 WINDOWPOS* wpos = (WINDOWPOS*)lParam;
george82ffc14a62006-09-05 06:51:41 +0000437 if ((wpos->flags & SWP_NOSIZE) || isAutoScaling())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000438 break;
439
george82f0775132007-01-27 15:48:25 +0000440 // Calculate the minimum size of main window
441 RECT r;
442 Rect min_size;
443 int tbMinWidth = 0, tbMinHeight = 0;
444 if (isToolbarEnabled()) {
445 tbMinWidth = tb.getTotalWidth();
446 tbMinHeight = tb.getHeight();
447 SetRect(&r, 0, 0, tbMinWidth, tbMinHeight);
448 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
449 min_size = Rect(r.left, r.top, r.right, r.bottom);
450 }
451
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000452 // Work out how big the window should ideally be
453 DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE);
454 DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL);
455 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
456
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000457 SetRect(&r, 0, 0, buffer->width(), buffer->height());
458 AdjustWindowRectEx(&r, style, FALSE, style_ex);
459 Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom);
460 if (current_style & WS_VSCROLL)
461 reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL);
462 if (current_style & WS_HSCROLL)
463 reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL);
464
465 SetRect(&r, reqd_size.tl.x, reqd_size.tl.y, reqd_size.br.x, reqd_size.br.y);
george82ced9db12006-09-11 09:09:14 +0000466 if (isToolbarEnabled())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000467 r.bottom += tb.getHeight();
468 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
469 reqd_size = Rect(r.left, r.top, r.right, r.bottom);
470
471 RECT current;
472 GetWindowRect(handle, &current);
473
george82f0775132007-01-27 15:48:25 +0000474 if (min_size.width() > reqd_size.width()) {
475 reqd_size.tl.x = min_size.tl.x;
476 reqd_size.br.x = min_size.br.x;
477 }
478 if (min_size.height() > reqd_size.height()) {
479 reqd_size.tl.y = min_size.tl.y;
480 reqd_size.br.y = min_size.br.y;
481 }
482
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000483 if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) {
george82f0775132007-01-27 15:48:25 +0000484 // Ensure that the window isn't resized too large or too small
485 if ((wpos->cx < min_size.width()) && isToolbarEnabled()) {
486 wpos->cx = min_size.width();
487 wpos->x = current.left;
488 } else if ((wpos->cx > reqd_size.width())) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000489 wpos->cx = reqd_size.width();
490 wpos->x = current.left;
491 }
george82f0775132007-01-27 15:48:25 +0000492 if ((wpos->cy < min_size.height()) && isToolbarEnabled()) {
493 wpos->cy = min_size.height();
494 wpos->y = current.top;
495 } else if (wpos->cy > reqd_size.height()) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000496 wpos->cy = reqd_size.height();
497 wpos->y = current.top;
498 }
499 }
500 }
501 break;
502
503 // Resize child windows and update window size info we have cached.
504
505 case WM_SIZE:
506 {
507 Point old_offset = desktopToClient(Point(0, 0));
508 RECT r;
509
510 // Resize child windows
511 GetClientRect(handle, &r);
512 if (tb.isVisible()) {
george82858a4642007-01-27 15:32:27 +0000513 MoveWindow(frameHandle, 0, tb.getHeight(), r.right, r.bottom - tb.getHeight(), TRUE);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000514 } else {
515 MoveWindow(frameHandle, 0, 0, r.right, r.bottom, TRUE);
516 }
517 tb.autoSize();
518
519 // Update the cached sizing information
520 GetWindowRect(frameHandle, &r);
521 window_size = Rect(r.left, r.top, r.right, r.bottom);
522 GetClientRect(frameHandle, &r);
523 client_size = Rect(r.left, r.top, r.right, r.bottom);
524
george82ffc14a62006-09-05 06:51:41 +0000525 // Perform the AutoScaling operation
526 if (isAutoScaling()) {
527 fitBufferToWindow(false);
528 } else {
529 // Determine whether scrollbars are required
530 calculateScrollBars();
531 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000532
533 // Redraw if required
george82ffc14a62006-09-05 06:51:41 +0000534 if ((!old_offset.equals(desktopToClient(Point(0, 0)))) || isAutoScaling())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000535 InvalidateRect(frameHandle, 0, TRUE);
536 }
537 break;
538
539 // -=- Bump-scrolling
540
541 case WM_TIMER:
542 switch (wParam) {
543 case TIMER_BUMPSCROLL:
544 if (!setViewportOffset(scrolloffset.translate(bumpScrollDelta)))
545 bumpScrollTimer.stop();
546 break;
547 case TIMER_POINTER_INTERVAL:
548 case TIMER_POINTER_3BUTTON:
549 ptr.handleTimer(callback, wParam);
550 break;
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000551 case TIMER_UPDATE:
552 updateWindow();
553 break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000554 }
555 break;
556
557 // -=- Track whether or not the window has focus
558
559 case WM_SETFOCUS:
560 has_focus = true;
561 break;
562 case WM_KILLFOCUS:
563 has_focus = false;
564 cursorOutsideBuffer();
565 // Restore the keyboard to a consistent state
566 kbd.releaseAllKeys(callback);
567 break;
568
569 // -=- If the menu is about to be shown, make sure it's up to date
570
571 case WM_INITMENU:
572 callback->refreshMenu(true);
573 break;
574
575 // -=- Handle the extra window menu items
576
577 // Pass system menu messages to the callback and only attempt
578 // to process them ourselves if the callback returns false.
579 case WM_SYSCOMMAND:
580 // Call the supplied callback
581 if (callback->sysCommand(wParam, lParam))
582 break;
583
584 // - Not processed by the callback, so process it as a system message
585 switch (wParam & 0xfff0) {
586
587 // When restored, ensure that full-screen mode is re-enabled if required.
588 case SC_RESTORE:
589 {
590 if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE) {
591 rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
592 setFullscreen(fullscreenRestore);
593 }
594 else if (fullscreenActive)
595 setFullscreen(false);
596 else
597 rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
598
599 return 0;
600 }
601
602 // If we are maximized or minimized then that cancels full-screen mode.
603 case SC_MINIMIZE:
604 case SC_MAXIMIZE:
605 fullscreenRestore = fullscreenActive;
606 setFullscreen(false);
607 break;
608
609 }
610 break;
611
612 // Treat all menu commands as system menu commands
613 case WM_COMMAND:
614 SendMessage(handle, WM_SYSCOMMAND, wParam, lParam);
615 return 0;
616
617 // -=- Handle keyboard input
618
619 case WM_KEYUP:
620 case WM_KEYDOWN:
621 // Hook the MenuKey to pop-up the window menu
622 if (menuKey && (wParam == menuKey)) {
623
624 bool ctrlDown = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
625 bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0;
626 bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
627 if (!(ctrlDown || altDown || shiftDown)) {
628
629 // If MenuKey is being released then pop-up the menu
630 if ((msg == WM_KEYDOWN)) {
631 // Make sure it's up to date
632 //
633 // NOTE: Here we call refreshMenu only to grey out Move and Size
634 // menu items. Other things will be refreshed once again
635 // while processing the WM_INITMENU message.
636 //
637 callback->refreshMenu(false);
638
639 // Show it under the pointer
640 POINT pt;
641 GetCursorPos(&pt);
642 cursorInBuffer = false;
643 TrackPopupMenu(GetSystemMenu(handle, FALSE),
644 TPM_CENTERALIGN | TPM_VCENTERALIGN, pt.x, pt.y, 0, handle, 0);
645 }
646
647 // Ignore the MenuKey keypress for both press & release events
648 return 0;
649 }
650 }
651 case WM_SYSKEYDOWN:
652 case WM_SYSKEYUP:
653 kbd.keyEvent(callback, wParam, lParam, (msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN));
654 return 0;
655
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000656 // -=- Handle mouse wheel scroll events
657
658#ifdef WM_MOUSEWHEEL
659 case WM_MOUSEWHEEL:
660 processMouseMessage(msg, wParam, lParam);
661 break;
662#endif
663
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000664 // -=- Handle the window closing
665
666 case WM_CLOSE:
667 vlog.debug("WM_CLOSE %x", handle);
668 callback->closeWindow();
669 break;
670
671 }
672
673 return rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
674}
675
676LRESULT
677DesktopWindow::processFrameMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
678 switch (msg) {
679
680 // -=- Paint the remote frame buffer
681
682 case WM_PAINT:
683 {
684 PAINTSTRUCT ps;
685 HDC paintDC = BeginPaint(frameHandle, &ps);
686 if (!paintDC)
687 throw rdr::SystemException("unable to BeginPaint", GetLastError());
688 Rect pr = Rect(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom);
689
690 if (!pr.is_empty()) {
691
692 // Draw using the correct palette
693 PaletteSelector pSel(paintDC, windowPalette.getHandle());
694
695 if (buffer->bitmap) {
696 // Update the bitmap's palette
697 if (palette_changed) {
698 palette_changed = false;
699 buffer->refreshPalette();
700 }
701
702 // Get device context
703 BitmapDC bitmapDC(paintDC, buffer->bitmap);
704
705 // Blit the border if required
706 Rect bufpos = desktopToClient(buffer->getRect());
707 if (!pr.enclosed_by(bufpos)) {
708 vlog.debug("draw border");
709 HBRUSH black = (HBRUSH) GetStockObject(BLACK_BRUSH);
710 RECT r;
711 SetRect(&r, 0, 0, bufpos.tl.x, client_size.height()); FillRect(paintDC, &r, black);
712 SetRect(&r, bufpos.tl.x, 0, bufpos.br.x, bufpos.tl.y); FillRect(paintDC, &r, black);
713 SetRect(&r, bufpos.br.x, 0, client_size.width(), client_size.height()); FillRect(paintDC, &r, black);
714 SetRect(&r, bufpos.tl.x, bufpos.br.y, bufpos.br.x, client_size.height()); FillRect(paintDC, &r, black);
715 }
716
717 // Do the blit
718 Point buf_pos = clientToDesktop(pr.tl);
719
720 if (!BitBlt(paintDC, pr.tl.x, pr.tl.y, pr.width(), pr.height(),
721 bitmapDC, buf_pos.x, buf_pos.y, SRCCOPY))
722 throw rdr::SystemException("unable to BitBlt to window", GetLastError());
723 }
724 }
725
726 EndPaint(frameHandle, &ps);
727
728 // - Notify the callback that a paint message has finished processing
729 callback->paintCompleted();
730 }
731 return 0;
732
733 // -=- Palette management
734
735 case WM_PALETTECHANGED:
736 vlog.debug("WM_PALETTECHANGED");
737 if ((HWND)wParam == frameHandle) {
738 vlog.debug("ignoring");
739 break;
740 }
741 case WM_QUERYNEWPALETTE:
742 vlog.debug("re-selecting palette");
743 {
744 WindowDC wdc(frameHandle);
745 PaletteSelector pSel(wdc, windowPalette.getHandle());
746 if (pSel.isRedrawRequired()) {
747 InvalidateRect(frameHandle, 0, FALSE);
748 UpdateWindow(frameHandle);
749 }
750 }
751 return TRUE;
752
753 case WM_VSCROLL:
754 case WM_HSCROLL:
755 {
756 Point delta;
757 int newpos = (msg == WM_VSCROLL) ? scrolloffset.y : scrolloffset.x;
758
759 switch (LOWORD(wParam)) {
760 case SB_PAGEUP: newpos -= 50; break;
761 case SB_PAGEDOWN: newpos += 50; break;
762 case SB_LINEUP: newpos -= 5; break;
763 case SB_LINEDOWN: newpos += 5; break;
764 case SB_THUMBTRACK:
765 case SB_THUMBPOSITION: newpos = HIWORD(wParam); break;
766 default: vlog.info("received unknown scroll message");
767 };
768
769 if (msg == WM_HSCROLL)
770 setViewportOffset(Point(newpos, scrolloffset.y));
771 else
772 setViewportOffset(Point(scrolloffset.x, newpos));
773
774 SCROLLINFO si;
775 si.cbSize = sizeof(si);
776 si.fMask = SIF_POS;
777 si.nPos = newpos;
778 SetScrollInfo(frameHandle, (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ, &si, TRUE);
779 }
780 break;
781
782 // -=- Cursor shape/visibility handling
783
784 case WM_SETCURSOR:
785 if (LOWORD(lParam) != HTCLIENT)
786 break;
787 SetCursor(cursorInBuffer ? dotCursor : arrowCursor);
788 return TRUE;
789
790 case WM_MOUSELEAVE:
791 trackingMouseLeave = false;
792 cursorOutsideBuffer();
793 return 0;
794
795 // -=- Mouse input handling
796
797 case WM_MOUSEMOVE:
798 case WM_LBUTTONUP:
799 case WM_MBUTTONUP:
800 case WM_RBUTTONUP:
801 case WM_LBUTTONDOWN:
802 case WM_MBUTTONDOWN:
803 case WM_RBUTTONDOWN:
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000804 processMouseMessage(msg, wParam, lParam);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000805 break;
806 }
807
808 return rfb::win32::SafeDefWindowProc(frameHandle, msg, wParam, lParam);
809}
810
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000811void
812DesktopWindow::processMouseMessage(UINT msg, WPARAM wParam, LPARAM lParam)
813{
814 if (!has_focus) {
815 cursorOutsideBuffer();
816 return;
817 }
818
819 if (!trackingMouseLeave) {
820 TRACKMOUSEEVENT tme;
821 tme.cbSize = sizeof(TRACKMOUSEEVENT);
822 tme.dwFlags = TME_LEAVE;
823 tme.hwndTrack = frameHandle;
824 _TrackMouseEvent(&tme);
825 trackingMouseLeave = true;
826 }
827 int mask = 0;
828 if (LOWORD(wParam) & MK_LBUTTON) mask |= 1;
829 if (LOWORD(wParam) & MK_MBUTTON) mask |= 2;
830 if (LOWORD(wParam) & MK_RBUTTON) mask |= 4;
831
832#ifdef WM_MOUSEWHEEL
833 if (msg == WM_MOUSEWHEEL) {
834 int delta = (short)HIWORD(wParam);
835 int repeats = (abs(delta)+119) / 120;
836 int wheelMask = (delta > 0) ? 8 : 16;
837 vlog.debug("repeats %d, mask %d\n",repeats,wheelMask);
838 for (int i=0; i<repeats; i++) {
839 ptr.pointerEvent(callback, oldpos, mask | wheelMask);
840 ptr.pointerEvent(callback, oldpos, mask);
841 }
842 } else {
843#endif
844 Point clientPos = Point(LOWORD(lParam), HIWORD(lParam));
845 Point p = clientToDesktop(clientPos);
846
847 // If the mouse is not within the server buffer area, do nothing
848 cursorInBuffer = buffer->getRect().contains(p);
849 if (!cursorInBuffer) {
850 cursorOutsideBuffer();
851 return;
852 }
853
854 // If we're locally rendering the cursor then redraw it
855 if (cursorAvailable) {
856 // - Render the cursor!
857 if (!p.equals(cursorPos)) {
858 hideLocalCursor();
859 cursorPos = p;
860 showLocalCursor();
861 if (cursorVisible)
862 hideSystemCursor();
863 }
864 }
865
866 // If we are doing bump-scrolling then try that first...
867 if (processBumpScroll(clientPos))
868 return;
869
870 // Send a pointer event to the server
871 oldpos = p;
872 if (buffer->isScaling()) {
george822446ed02007-03-10 08:55:35 +0000873 p.x /= buffer->getScaleRatioX();
874 p.y /= buffer->getScaleRatioY();
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000875 }
876 ptr.pointerEvent(callback, p, mask);
877#ifdef WM_MOUSEWHEEL
878 }
879#endif
880}
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000881
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000882void DesktopWindow::updateWindow()
883{
884 Rect rect;
885
886 updateTimer.stop();
887
888 rect = damage.get_bounding_rect();
889 damage.clear();
890
891 RECT invalid = {rect.tl.x, rect.tl.y, rect.br.x, rect.br.y};
892 InvalidateRect(frameHandle, &invalid, FALSE);
893}
894
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000895void
896DesktopWindow::hideLocalCursor() {
897 // - Blit the cursor backing store over the cursor
898 // *** ALWAYS call this BEFORE changing buffer PF!!!
899 if (cursorVisible) {
900 cursorVisible = false;
901 buffer->DIBSectionBuffer::imageRect(cursorBackingRect, cursorBacking.data);
902 invalidateDesktopRect(cursorBackingRect, false);
903 }
904}
905
906void
907DesktopWindow::showLocalCursor() {
908 if (cursorAvailable && !cursorVisible && cursorInBuffer) {
george827c721cc2006-09-23 07:09:37 +0000909 if (!buffer->getScaledPixelFormat().equal(cursor.getPF()) ||
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000910 cursor.getRect().is_empty()) {
911 vlog.info("attempting to render invalid local cursor");
912 cursorAvailable = false;
913 showSystemCursor();
914 return;
915 }
916 cursorVisible = true;
917
918 cursorBackingRect = cursor.getRect().translate(cursorPos).translate(cursor.hotspot.negate());
919 cursorBackingRect = cursorBackingRect.intersect(buffer->getRect());
920 buffer->getImage(cursorBacking.data, cursorBackingRect);
921
922 renderLocalCursor();
923
924 invalidateDesktopRect(cursorBackingRect, false);
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000925 // Since we render the cursor onto the framebuffer, we need to update
926 // right away to get a responsive cursor.
927 updateWindow();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000928 }
929}
930
931void DesktopWindow::cursorOutsideBuffer()
932{
933 cursorInBuffer = false;
934 hideLocalCursor();
935 showSystemCursor();
936}
937
938void
939DesktopWindow::renderLocalCursor()
940{
941 Rect r = cursor.getRect();
942 r = r.translate(cursorPos).translate(cursor.hotspot.negate());
943 buffer->DIBSectionBuffer::maskRect(r, cursor.data, cursor.mask.buf);
944}
945
946void
947DesktopWindow::hideSystemCursor() {
948 if (systemCursorVisible) {
949 vlog.debug("hide system cursor");
950 systemCursorVisible = false;
951 ShowCursor(FALSE);
952 }
953}
954
955void
956DesktopWindow::showSystemCursor() {
957 if (!systemCursorVisible) {
958 vlog.debug("show system cursor");
959 systemCursorVisible = true;
960 ShowCursor(TRUE);
961 }
962}
963
964
965bool
966DesktopWindow::invalidateDesktopRect(const Rect& crect, bool scaling) {
967 Rect rect;
968 if (buffer->isScaling() && scaling) {
969 rect = desktopToClient(buffer->calculateScaleBoundary(crect));
970 } else rect = desktopToClient(crect);
971 if (rect.intersect(client_size).is_empty()) return false;
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000972 damage.assign_union(rfb::Region(rect));
973 if (!updateTimer.isActive())
974 updateTimer.start(100);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000975 return true;
976}
977
978
979void
980DesktopWindow::notifyClipboardChanged(const char* text, int len) {
981 callback->clientCutText(text, len);
982}
983
984
985void
986DesktopWindow::setPF(const PixelFormat& pf) {
987 // If the cursor is the wrong format then clear it
george827c721cc2006-09-23 07:09:37 +0000988 if (!pf.equal(buffer->getScaledPixelFormat()))
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000989 setCursor(0, 0, Point(), 0, 0);
990
991 // Update the desktop buffer
992 buffer->setPF(pf);
993
994 // Redraw the window
995 InvalidateRect(frameHandle, 0, FALSE);
996}
997
998void
999DesktopWindow::setSize(int w, int h) {
1000 vlog.debug("setSize %dx%d", w, h);
1001
1002 // If the locally-rendered cursor is visible then remove it
1003 hideLocalCursor();
1004
1005 // Resize the backing buffer
1006 buffer->setSize(w, h);
1007
george82ffc14a62006-09-05 06:51:41 +00001008 // Calculate the pixel buffer aspect correlation. It's used
1009 // for the autoScaling operation.
1010 aspect_corr = (double)w / h;
1011
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001012 // If the window is not maximised or full-screen then resize it
1013 if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) {
1014 // Resize the window to the required size
1015 RECT r = {0, 0, w, h};
1016 AdjustWindowRectEx(&r, GetWindowLong(frameHandle, GWL_STYLE), FALSE,
1017 GetWindowLong(frameHandle, GWL_EXSTYLE));
george82ced9db12006-09-11 09:09:14 +00001018 if (isToolbarEnabled())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001019 r.bottom += tb.getHeight();
1020 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
1021
1022 // Resize about the center of the window, and clip to current monitor
1023 MonitorInfo mi(handle);
1024 resizeWindow(handle, r.right-r.left, r.bottom-r.top);
1025 mi.clipTo(handle);
1026 } else {
1027 // Ensure the screen contents are consistent
1028 InvalidateRect(frameHandle, 0, FALSE);
1029 }
1030
1031 // Enable/disable scrollbars as appropriate
1032 calculateScrollBars();
1033}
1034
george8274ea5f32006-09-11 11:40:12 +00001035void DesktopWindow::setAutoScaling(bool as) {
1036 autoScaling = as;
george822446ed02007-03-10 08:55:35 +00001037 if (isToolbarEnabled()) refreshToolbarButtons();
george8274ea5f32006-09-11 11:40:12 +00001038 if (as) fitBufferToWindow();
1039}
1040
george822446ed02007-03-10 08:55:35 +00001041void DesktopWindow::setDesktopScale(int scale_) {
1042 if (buffer->getScale() == scale_ || scale_ <= 0) return;
george824880eec2007-03-19 10:55:13 +00001043 bool state = buffer->isScaling();
george822446ed02007-03-10 08:55:35 +00001044 buffer->setScale(scale_);
george824880eec2007-03-19 10:55:13 +00001045 state ^= buffer->isScaling();
1046 if (state) convertCursorToBuffer();
george8274ea5f32006-09-11 11:40:12 +00001047 if (isToolbarEnabled()) refreshToolbarButtons();
george82db5a10f2007-03-20 04:31:32 +00001048 if (!(isAutoScaling() || isFullscreen() || (GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE))) resizeDesktopWindowToBuffer();
george82770bbbc2007-03-12 10:48:09 +00001049 printScale();
george82858a4642007-01-27 15:32:27 +00001050 InvalidateRect(frameHandle, 0, FALSE);
george8204a77712006-05-29 14:18:14 +00001051}
1052
george82c4eb6262007-03-20 10:54:38 +00001053void DesktopWindow::setDesktopScaleFilter(unsigned int scaleFilterID) {
1054 if (scaleFilterID == getDesktopScaleFilterID() || scaleFilterID > scaleFilterMaxNumber) return;
1055 buffer->setScaleFilter(scaleFilterID);
1056 InvalidateRect(frameHandle, 0, FALSE);
1057}
1058
george824880eec2007-03-19 10:55:13 +00001059void DesktopWindow::convertCursorToBuffer() {
1060 if (memcmp(&(cursor.getPF()), &(buffer->getPF()), sizeof(PixelBuffer)) == 0) return;
1061 internalSetCursor = true;
1062 setCursor(cursorWidth, cursorHeight, cursorHotspot, cursorImage, cursorMask);
1063 internalSetCursor = false;
1064}
1065
george823c68f5f2006-09-05 06:17:01 +00001066void DesktopWindow::fitBufferToWindow(bool repaint) {
1067 double scale_ratio;
1068 double resized_aspect_corr = double(client_size.width()) / client_size.height();
1069 DWORD style = GetWindowLong(frameHandle, GWL_STYLE);
1070 if (style & (WS_VSCROLL | WS_HSCROLL)) {
1071 style &= ~(WS_VSCROLL | WS_HSCROLL);
1072 SetWindowLong(frameHandle, GWL_STYLE, style);
1073 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1074 // Update the cached client size
1075 RECT r;
1076 GetClientRect(frameHandle, &r);
1077 client_size = Rect(r.left, r.top, r.right, r.bottom);
1078 }
george824880eec2007-03-19 10:55:13 +00001079 bool state = buffer->isScaling();
george823c68f5f2006-09-05 06:17:01 +00001080 if (resized_aspect_corr > aspect_corr) {
george82770bbbc2007-03-12 10:48:09 +00001081 scale_ratio = (double)client_size.height() / buffer->getSrcHeight();
1082 buffer->setScaleWindowSize(ceil(buffer->getSrcWidth()*scale_ratio), client_size.height());
george823c68f5f2006-09-05 06:17:01 +00001083 } else {
george82770bbbc2007-03-12 10:48:09 +00001084 scale_ratio = (double)client_size.width() / buffer->getSrcWidth();
1085 buffer->setScaleWindowSize(client_size.width(), ceil(buffer->getSrcHeight()*scale_ratio));
george823c68f5f2006-09-05 06:17:01 +00001086 }
george824880eec2007-03-19 10:55:13 +00001087 state ^= buffer->isScaling();
1088 if (state) convertCursorToBuffer();
george82770bbbc2007-03-12 10:48:09 +00001089 printScale();
1090 InvalidateRect(frameHandle, 0, FALSE);
1091}
1092
1093void DesktopWindow::printScale() {
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001094 setName(desktopName);
george823c68f5f2006-09-05 06:17:01 +00001095}
1096
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001097void
1098DesktopWindow::setCursor(int w, int h, const Point& hotspot, void* data, void* mask) {
1099 hideLocalCursor();
1100
1101 cursor.hotspot = hotspot;
1102
1103 cursor.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001104 cursor.setPF(buffer->getScaledPixelFormat());
george824880eec2007-03-19 10:55:13 +00001105
1106 // Convert the current cursor pixel format to bpp32 if scaling mode is on.
1107 // It need because ScaledDIBSection buffer always works with bpp32 pixel data
1108 // in scaling mode.
1109 if (buffer->isScaling()) {
1110 U8 *ptr = (U8*)cursor.data;
1111 U8 *dataPtr = (U8*)data;
1112 U32 pixel = 0;
1113 int bytesPerPixel = buffer->getPixelFormat().bpp / 8;
1114 int pixelCount = w * h;
1115 PixelFormat pf = buffer->getPixelFormat();
1116
1117 while (pixelCount--) {
1118 if (bytesPerPixel == 1) {
1119 pixel = *dataPtr++;
1120 } else if (bytesPerPixel == 2) {
1121 int b0 = *dataPtr++; int b1 = *dataPtr++;
1122 pixel = b1 << 8 | b0;
1123 } else if (bytesPerPixel == 4) {
1124 int b0 = *dataPtr++; int b1 = *dataPtr++;
1125 int b2 = *dataPtr++; int b3 = *dataPtr++;
1126 pixel = b3 << 24 | b2 << 16 | b1 << 8 | b0;
1127 } else {
1128 pixel = 0;
1129 }
1130 *ptr++ = (U8)((((pixel >> pf.blueShift ) & pf.blueMax ) * 255 + pf.blueMax /2) / pf.blueMax);
1131 *ptr++ = (U8)((((pixel >> pf.greenShift) & pf.greenMax) * 255 + pf.greenMax/2) / pf.greenMax);
1132 *ptr++ = (U8)((((pixel >> pf.redShift ) & pf.redMax ) * 255 + pf.redMax /2) / pf.redMax);
1133 *ptr++ = (U8)0;
1134 }
1135 } else {
1136 cursor.imageRect(cursor.getRect(), data);
1137 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001138 memcpy(cursor.mask.buf, mask, cursor.maskLen());
1139 cursor.crop();
1140
1141 cursorBacking.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001142 cursorBacking.setPF(buffer->getScaledPixelFormat());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001143
1144 cursorAvailable = true;
1145
1146 showLocalCursor();
george824880eec2007-03-19 10:55:13 +00001147
1148 // Save the cursor parameters
1149 if (!internalSetCursor) {
1150 if (cursorImage) delete [] cursorImage;
1151 if (cursorMask) delete [] cursorMask;
1152 int cursorImageSize = (buffer->getPixelFormat().bpp/8) * w * h;
1153 cursorImage = new U8[cursorImageSize];
1154 cursorMask = new U8[cursor.maskLen()];
1155 memcpy(cursorImage, data, cursorImageSize);
1156 memcpy(cursorMask, mask, cursor.maskLen());
1157 cursorWidth = w;
1158 cursorHeight = h;
1159 cursorHotspot = hotspot;
1160 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001161}
1162
1163PixelFormat
1164DesktopWindow::getNativePF() const {
1165 vlog.debug("getNativePF()");
1166 return WindowDC(handle).getPF();
1167}
1168
1169
1170void
1171DesktopWindow::refreshWindowPalette(int start, int count) {
1172 vlog.debug("refreshWindowPalette(%d, %d)", start, count);
1173
1174 Colour colours[256];
1175 if (count > 256) {
1176 vlog.debug("%d palette entries", count);
1177 throw rdr::Exception("too many palette entries");
1178 }
1179
1180 // Copy the palette from the DIBSectionBuffer
1181 ColourMap* cm = buffer->getColourMap();
1182 if (!cm) return;
1183 for (int i=0; i<count; i++) {
1184 int r, g, b;
1185 cm->lookup(i, &r, &g, &b);
1186 colours[i].r = r;
1187 colours[i].g = g;
1188 colours[i].b = b;
1189 }
1190
1191 // Set the window palette
1192 windowPalette.setEntries(start, count, colours);
1193
1194 // Cause the window to be redrawn
1195 palette_changed = true;
1196 InvalidateRect(handle, 0, FALSE);
1197}
1198
1199
1200void DesktopWindow::calculateScrollBars() {
1201 // Calculate the required size of window
1202 DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE);
1203 DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL);
1204 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1205 DWORD old_style;
1206 RECT r;
1207 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1208 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1209 Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom);
1210
1211 if (!bumpScroll) {
1212 // We only enable scrollbars if bump-scrolling is not active.
1213 // Effectively, this means if full-screen is not active,
1214 // but I think it's better to make these things explicit.
1215
1216 // Work out whether scroll bars are required
1217 do {
1218 old_style = style;
1219
1220 if (!(style & WS_HSCROLL) && (reqd_size.width() > window_size.width())) {
1221 style |= WS_HSCROLL;
1222 reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL);
1223 }
1224 if (!(style & WS_VSCROLL) && (reqd_size.height() > window_size.height())) {
1225 style |= WS_VSCROLL;
1226 reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL);
1227 }
1228 } while (style != old_style);
1229 }
1230
1231 // Tell Windows to update the window style & cached settings
1232 if (style != current_style) {
1233 SetWindowLong(frameHandle, GWL_STYLE, style);
1234 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1235 }
1236
1237 // Update the scroll settings
1238 SCROLLINFO si;
1239 if (style & WS_VSCROLL) {
1240 si.cbSize = sizeof(si);
1241 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1242 si.nMin = 0;
1243 si.nMax = buffer->height();
1244 si.nPage = buffer->height() - (reqd_size.height() - window_size.height());
Peter Åstrand1cd3dda2008-12-09 11:17:28 +00001245 maxscrolloffset.y = __rfbmax(0, si.nMax-si.nPage);
1246 scrolloffset.y = __rfbmin(maxscrolloffset.y, scrolloffset.y);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001247 si.nPos = scrolloffset.y;
1248 SetScrollInfo(frameHandle, SB_VERT, &si, TRUE);
1249 }
1250 if (style & WS_HSCROLL) {
1251 si.cbSize = sizeof(si);
1252 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1253 si.nMin = 0;
1254 si.nMax = buffer->width();
1255 si.nPage = buffer->width() - (reqd_size.width() - window_size.width());
Peter Åstrand1cd3dda2008-12-09 11:17:28 +00001256 maxscrolloffset.x = __rfbmax(0, si.nMax-si.nPage);
1257 scrolloffset.x = __rfbmin(maxscrolloffset.x, scrolloffset.x);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001258 si.nPos = scrolloffset.x;
1259 SetScrollInfo(frameHandle, SB_HORZ, &si, TRUE);
1260 }
1261
1262 // Update the cached client size
1263 GetClientRect(frameHandle, &r);
1264 client_size = Rect(r.left, r.top, r.right, r.bottom);
1265}
1266
george82858a4642007-01-27 15:32:27 +00001267void DesktopWindow::resizeDesktopWindowToBuffer() {
1268 RECT r;
1269 DWORD style = GetWindowLong(frameHandle, GWL_STYLE) & ~(WS_VSCROLL | WS_HSCROLL);
1270 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1271
1272 // Calculate the required size of the desktop window
1273 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1274 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1275 if (isToolbarEnabled())
1276 r.bottom += tb.getHeight();
1277 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
1278
1279 // Set the required size, center the main window and clip to the current monitor
1280 SetWindowPos(handle, 0, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
1281 centerWindow(handle, NULL);
1282 MonitorInfo mi(getMonitor());
1283 mi.clipTo(handle);
1284
1285 // Enable/disable scrollbars as appropriate
1286 calculateScrollBars();
1287}
1288
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001289
Pierre Ossmanb2ff1602009-03-25 12:13:28 +00001290void DesktopWindow::framebufferUpdateEnd()
1291{
1292 updateWindow();
1293}
1294
1295
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001296void
1297DesktopWindow::setName(const char* name) {
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001298 if (name != desktopName) {
1299 strCopy(desktopName, name, sizeof(desktopName));
1300 }
1301 char *newTitle = new char[strlen(desktopName)+20];
Peter Åstrand4eacc022009-02-27 10:12:14 +00001302 sprintf(newTitle, "TigerVNC: %.240s @ %i%%", desktopName, getDesktopScale());
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001303 SetWindowText(handle, TStr(newTitle));
1304 delete [] newTitle;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001305}
1306
1307
1308void
Adam Tkacacf6c6b2009-02-13 12:42:05 +00001309DesktopWindow::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001310 CharArray t(len+1);
1311 memcpy(t.buf, str, len);
1312 t.buf[len] = 0;
1313 clipboard.setClipText(t.buf);
1314}
1315
1316
1317void DesktopWindow::fillRect(const Rect& r, Pixel pix) {
1318 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1319 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1320 buffer->fillRect(r, pix);
1321 invalidateDesktopRect(r);
1322}
1323void DesktopWindow::imageRect(const Rect& r, void* pixels) {
1324 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1325 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1326 buffer->imageRect(r, pixels);
1327 invalidateDesktopRect(r);
1328}
1329void DesktopWindow::copyRect(const Rect& r, int srcX, int srcY) {
1330 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1331 if (cursorBackingRect.overlaps(img_rect) ||
1332 cursorBackingRect.overlaps(Rect(srcX, srcY, srcX+img_rect.width(), srcY+img_rect.height())))
1333 hideLocalCursor();
1334 buffer->copyRect(r, Point(r.tl.x-srcX, r.tl.y-srcY));
1335 invalidateDesktopRect(r);
1336}
1337
1338void DesktopWindow::invertRect(const Rect& r) {
1339 int stride;
1340 rdr::U8* p = buffer->isScaling() ? buffer->getPixelsRW(buffer->calculateScaleBoundary(r), &stride)
1341 : buffer->getPixelsRW(r, &stride);
1342 for (int y = 0; y < r.height(); y++) {
1343 for (int x = 0; x < r.width(); x++) {
1344 switch (buffer->getPF().bpp) {
1345 case 8: ((rdr::U8* )p)[x+y*stride] ^= 0xff; break;
1346 case 16: ((rdr::U16*)p)[x+y*stride] ^= 0xffff; break;
1347 case 32: ((rdr::U32*)p)[x+y*stride] ^= 0xffffffff; break;
1348 }
1349 }
1350 }
1351 invalidateDesktopRect(r);
1352}