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