blob: d60ff8fe500fc11e60150e4562db3797b7796b54 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
DRCc75dc442010-05-20 07:44:49 +00002 * Copyright (C) 2010 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
268 // Find the size of the display the window is on
269 MonitorInfo mi(handle);
270
271 // Hide the toolbar
272 if (tb.isVisible())
273 tb.hide();
274 SetWindowLong(frameHandle, GWL_EXSTYLE, 0);
275
276 // Set the window full-screen
277 DWORD flags = GetWindowLong(handle, GWL_STYLE);
278 fullscreenOldFlags = flags;
279 flags = flags & ~(WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZE | WS_MINIMIZE);
280 vlog.debug("flags=%x", flags);
281
282 SetWindowLong(handle, GWL_STYLE, flags);
george82ced9db12006-09-11 09:09:14 +0000283 SetWindowPos(handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000284 mi.rcMonitor.right-mi.rcMonitor.left,
285 mi.rcMonitor.bottom-mi.rcMonitor.top,
286 SWP_FRAMECHANGED);
287 } 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);
728
729 // - Notify the callback that a paint message has finished processing
730 callback->paintCompleted();
731 }
732 return 0;
733
734 // -=- Palette management
735
736 case WM_PALETTECHANGED:
737 vlog.debug("WM_PALETTECHANGED");
738 if ((HWND)wParam == frameHandle) {
739 vlog.debug("ignoring");
740 break;
741 }
742 case WM_QUERYNEWPALETTE:
743 vlog.debug("re-selecting palette");
744 {
745 WindowDC wdc(frameHandle);
746 PaletteSelector pSel(wdc, windowPalette.getHandle());
747 if (pSel.isRedrawRequired()) {
748 InvalidateRect(frameHandle, 0, FALSE);
749 UpdateWindow(frameHandle);
750 }
751 }
752 return TRUE;
753
754 case WM_VSCROLL:
755 case WM_HSCROLL:
756 {
757 Point delta;
758 int newpos = (msg == WM_VSCROLL) ? scrolloffset.y : scrolloffset.x;
759
760 switch (LOWORD(wParam)) {
761 case SB_PAGEUP: newpos -= 50; break;
762 case SB_PAGEDOWN: newpos += 50; break;
763 case SB_LINEUP: newpos -= 5; break;
764 case SB_LINEDOWN: newpos += 5; break;
765 case SB_THUMBTRACK:
766 case SB_THUMBPOSITION: newpos = HIWORD(wParam); break;
767 default: vlog.info("received unknown scroll message");
768 };
769
770 if (msg == WM_HSCROLL)
771 setViewportOffset(Point(newpos, scrolloffset.y));
772 else
773 setViewportOffset(Point(scrolloffset.x, newpos));
774
775 SCROLLINFO si;
776 si.cbSize = sizeof(si);
777 si.fMask = SIF_POS;
778 si.nPos = newpos;
779 SetScrollInfo(frameHandle, (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ, &si, TRUE);
780 }
781 break;
782
783 // -=- Cursor shape/visibility handling
784
785 case WM_SETCURSOR:
786 if (LOWORD(lParam) != HTCLIENT)
787 break;
788 SetCursor(cursorInBuffer ? dotCursor : arrowCursor);
789 return TRUE;
790
791 case WM_MOUSELEAVE:
792 trackingMouseLeave = false;
793 cursorOutsideBuffer();
794 return 0;
795
796 // -=- Mouse input handling
797
798 case WM_MOUSEMOVE:
799 case WM_LBUTTONUP:
800 case WM_MBUTTONUP:
801 case WM_RBUTTONUP:
802 case WM_LBUTTONDOWN:
803 case WM_MBUTTONDOWN:
804 case WM_RBUTTONDOWN:
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000805 processMouseMessage(msg, wParam, lParam);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000806 break;
807 }
808
809 return rfb::win32::SafeDefWindowProc(frameHandle, msg, wParam, lParam);
810}
811
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000812void
813DesktopWindow::processMouseMessage(UINT msg, WPARAM wParam, LPARAM lParam)
814{
815 if (!has_focus) {
816 cursorOutsideBuffer();
817 return;
818 }
819
820 if (!trackingMouseLeave) {
821 TRACKMOUSEEVENT tme;
822 tme.cbSize = sizeof(TRACKMOUSEEVENT);
823 tme.dwFlags = TME_LEAVE;
824 tme.hwndTrack = frameHandle;
825 _TrackMouseEvent(&tme);
826 trackingMouseLeave = true;
827 }
828 int mask = 0;
829 if (LOWORD(wParam) & MK_LBUTTON) mask |= 1;
830 if (LOWORD(wParam) & MK_MBUTTON) mask |= 2;
831 if (LOWORD(wParam) & MK_RBUTTON) mask |= 4;
832
833#ifdef WM_MOUSEWHEEL
834 if (msg == WM_MOUSEWHEEL) {
835 int delta = (short)HIWORD(wParam);
836 int repeats = (abs(delta)+119) / 120;
837 int wheelMask = (delta > 0) ? 8 : 16;
838 vlog.debug("repeats %d, mask %d\n",repeats,wheelMask);
839 for (int i=0; i<repeats; i++) {
840 ptr.pointerEvent(callback, oldpos, mask | wheelMask);
841 ptr.pointerEvent(callback, oldpos, mask);
842 }
843 } else {
844#endif
845 Point clientPos = Point(LOWORD(lParam), HIWORD(lParam));
846 Point p = clientToDesktop(clientPos);
847
848 // If the mouse is not within the server buffer area, do nothing
849 cursorInBuffer = buffer->getRect().contains(p);
850 if (!cursorInBuffer) {
851 cursorOutsideBuffer();
852 return;
853 }
854
855 // If we're locally rendering the cursor then redraw it
856 if (cursorAvailable) {
857 // - Render the cursor!
858 if (!p.equals(cursorPos)) {
859 hideLocalCursor();
860 cursorPos = p;
861 showLocalCursor();
862 if (cursorVisible)
863 hideSystemCursor();
864 }
865 }
866
867 // If we are doing bump-scrolling then try that first...
868 if (processBumpScroll(clientPos))
869 return;
870
871 // Send a pointer event to the server
872 oldpos = p;
873 if (buffer->isScaling()) {
george822446ed02007-03-10 08:55:35 +0000874 p.x /= buffer->getScaleRatioX();
875 p.y /= buffer->getScaleRatioY();
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000876 }
877 ptr.pointerEvent(callback, p, mask);
878#ifdef WM_MOUSEWHEEL
879 }
880#endif
881}
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000882
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000883void DesktopWindow::updateWindow()
884{
885 Rect rect;
886
887 updateTimer.stop();
888
889 rect = damage.get_bounding_rect();
890 damage.clear();
891
892 RECT invalid = {rect.tl.x, rect.tl.y, rect.br.x, rect.br.y};
893 InvalidateRect(frameHandle, &invalid, FALSE);
894}
895
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000896void
897DesktopWindow::hideLocalCursor() {
898 // - Blit the cursor backing store over the cursor
899 // *** ALWAYS call this BEFORE changing buffer PF!!!
900 if (cursorVisible) {
901 cursorVisible = false;
902 buffer->DIBSectionBuffer::imageRect(cursorBackingRect, cursorBacking.data);
903 invalidateDesktopRect(cursorBackingRect, false);
904 }
905}
906
907void
908DesktopWindow::showLocalCursor() {
909 if (cursorAvailable && !cursorVisible && cursorInBuffer) {
george827c721cc2006-09-23 07:09:37 +0000910 if (!buffer->getScaledPixelFormat().equal(cursor.getPF()) ||
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000911 cursor.getRect().is_empty()) {
912 vlog.info("attempting to render invalid local cursor");
913 cursorAvailable = false;
914 showSystemCursor();
915 return;
916 }
917 cursorVisible = true;
918
919 cursorBackingRect = cursor.getRect().translate(cursorPos).translate(cursor.hotspot.negate());
920 cursorBackingRect = cursorBackingRect.intersect(buffer->getRect());
921 buffer->getImage(cursorBacking.data, cursorBackingRect);
922
923 renderLocalCursor();
924
925 invalidateDesktopRect(cursorBackingRect, false);
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000926 // Since we render the cursor onto the framebuffer, we need to update
927 // right away to get a responsive cursor.
928 updateWindow();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000929 }
930}
931
932void DesktopWindow::cursorOutsideBuffer()
933{
934 cursorInBuffer = false;
935 hideLocalCursor();
936 showSystemCursor();
937}
938
939void
940DesktopWindow::renderLocalCursor()
941{
942 Rect r = cursor.getRect();
943 r = r.translate(cursorPos).translate(cursor.hotspot.negate());
944 buffer->DIBSectionBuffer::maskRect(r, cursor.data, cursor.mask.buf);
945}
946
947void
948DesktopWindow::hideSystemCursor() {
949 if (systemCursorVisible) {
950 vlog.debug("hide system cursor");
951 systemCursorVisible = false;
952 ShowCursor(FALSE);
953 }
954}
955
956void
957DesktopWindow::showSystemCursor() {
958 if (!systemCursorVisible) {
959 vlog.debug("show system cursor");
960 systemCursorVisible = true;
961 ShowCursor(TRUE);
962 }
963}
964
965
966bool
967DesktopWindow::invalidateDesktopRect(const Rect& crect, bool scaling) {
968 Rect rect;
969 if (buffer->isScaling() && scaling) {
970 rect = desktopToClient(buffer->calculateScaleBoundary(crect));
971 } else rect = desktopToClient(crect);
972 if (rect.intersect(client_size).is_empty()) return false;
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000973 damage.assign_union(rfb::Region(rect));
974 if (!updateTimer.isActive())
975 updateTimer.start(100);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000976 return true;
977}
978
979
980void
981DesktopWindow::notifyClipboardChanged(const char* text, int len) {
982 callback->clientCutText(text, len);
983}
984
985
986void
987DesktopWindow::setPF(const PixelFormat& pf) {
988 // If the cursor is the wrong format then clear it
george827c721cc2006-09-23 07:09:37 +0000989 if (!pf.equal(buffer->getScaledPixelFormat()))
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000990 setCursor(0, 0, Point(), 0, 0);
991
992 // Update the desktop buffer
993 buffer->setPF(pf);
994
995 // Redraw the window
996 InvalidateRect(frameHandle, 0, FALSE);
997}
998
999void
1000DesktopWindow::setSize(int w, int h) {
1001 vlog.debug("setSize %dx%d", w, h);
1002
1003 // If the locally-rendered cursor is visible then remove it
1004 hideLocalCursor();
1005
1006 // Resize the backing buffer
1007 buffer->setSize(w, h);
1008
george82ffc14a62006-09-05 06:51:41 +00001009 // Calculate the pixel buffer aspect correlation. It's used
1010 // for the autoScaling operation.
1011 aspect_corr = (double)w / h;
1012
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001013 // If the window is not maximised or full-screen then resize it
1014 if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) {
1015 // Resize the window to the required size
1016 RECT r = {0, 0, w, h};
1017 AdjustWindowRectEx(&r, GetWindowLong(frameHandle, GWL_STYLE), FALSE,
1018 GetWindowLong(frameHandle, GWL_EXSTYLE));
george82ced9db12006-09-11 09:09:14 +00001019 if (isToolbarEnabled())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001020 r.bottom += tb.getHeight();
1021 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
1022
1023 // Resize about the center of the window, and clip to current monitor
1024 MonitorInfo mi(handle);
1025 resizeWindow(handle, r.right-r.left, r.bottom-r.top);
1026 mi.clipTo(handle);
1027 } else {
1028 // Ensure the screen contents are consistent
1029 InvalidateRect(frameHandle, 0, FALSE);
1030 }
1031
1032 // Enable/disable scrollbars as appropriate
1033 calculateScrollBars();
1034}
1035
george8274ea5f32006-09-11 11:40:12 +00001036void DesktopWindow::setAutoScaling(bool as) {
1037 autoScaling = as;
george822446ed02007-03-10 08:55:35 +00001038 if (isToolbarEnabled()) refreshToolbarButtons();
george8274ea5f32006-09-11 11:40:12 +00001039 if (as) fitBufferToWindow();
1040}
1041
george822446ed02007-03-10 08:55:35 +00001042void DesktopWindow::setDesktopScale(int scale_) {
1043 if (buffer->getScale() == scale_ || scale_ <= 0) return;
george824880eec2007-03-19 10:55:13 +00001044 bool state = buffer->isScaling();
george822446ed02007-03-10 08:55:35 +00001045 buffer->setScale(scale_);
george824880eec2007-03-19 10:55:13 +00001046 state ^= buffer->isScaling();
1047 if (state) convertCursorToBuffer();
george8274ea5f32006-09-11 11:40:12 +00001048 if (isToolbarEnabled()) refreshToolbarButtons();
george82db5a10f2007-03-20 04:31:32 +00001049 if (!(isAutoScaling() || isFullscreen() || (GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE))) resizeDesktopWindowToBuffer();
george82770bbbc2007-03-12 10:48:09 +00001050 printScale();
george82858a4642007-01-27 15:32:27 +00001051 InvalidateRect(frameHandle, 0, FALSE);
george8204a77712006-05-29 14:18:14 +00001052}
1053
george82c4eb6262007-03-20 10:54:38 +00001054void DesktopWindow::setDesktopScaleFilter(unsigned int scaleFilterID) {
1055 if (scaleFilterID == getDesktopScaleFilterID() || scaleFilterID > scaleFilterMaxNumber) return;
1056 buffer->setScaleFilter(scaleFilterID);
1057 InvalidateRect(frameHandle, 0, FALSE);
1058}
1059
george824880eec2007-03-19 10:55:13 +00001060void DesktopWindow::convertCursorToBuffer() {
1061 if (memcmp(&(cursor.getPF()), &(buffer->getPF()), sizeof(PixelBuffer)) == 0) return;
1062 internalSetCursor = true;
1063 setCursor(cursorWidth, cursorHeight, cursorHotspot, cursorImage, cursorMask);
1064 internalSetCursor = false;
1065}
1066
george823c68f5f2006-09-05 06:17:01 +00001067void DesktopWindow::fitBufferToWindow(bool repaint) {
1068 double scale_ratio;
1069 double resized_aspect_corr = double(client_size.width()) / client_size.height();
1070 DWORD style = GetWindowLong(frameHandle, GWL_STYLE);
1071 if (style & (WS_VSCROLL | WS_HSCROLL)) {
1072 style &= ~(WS_VSCROLL | WS_HSCROLL);
1073 SetWindowLong(frameHandle, GWL_STYLE, style);
1074 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1075 // Update the cached client size
1076 RECT r;
1077 GetClientRect(frameHandle, &r);
1078 client_size = Rect(r.left, r.top, r.right, r.bottom);
1079 }
george824880eec2007-03-19 10:55:13 +00001080 bool state = buffer->isScaling();
george823c68f5f2006-09-05 06:17:01 +00001081 if (resized_aspect_corr > aspect_corr) {
george82770bbbc2007-03-12 10:48:09 +00001082 scale_ratio = (double)client_size.height() / buffer->getSrcHeight();
1083 buffer->setScaleWindowSize(ceil(buffer->getSrcWidth()*scale_ratio), client_size.height());
george823c68f5f2006-09-05 06:17:01 +00001084 } else {
george82770bbbc2007-03-12 10:48:09 +00001085 scale_ratio = (double)client_size.width() / buffer->getSrcWidth();
1086 buffer->setScaleWindowSize(client_size.width(), ceil(buffer->getSrcHeight()*scale_ratio));
george823c68f5f2006-09-05 06:17:01 +00001087 }
george824880eec2007-03-19 10:55:13 +00001088 state ^= buffer->isScaling();
1089 if (state) convertCursorToBuffer();
george82770bbbc2007-03-12 10:48:09 +00001090 printScale();
1091 InvalidateRect(frameHandle, 0, FALSE);
1092}
1093
1094void DesktopWindow::printScale() {
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001095 setName(desktopName);
george823c68f5f2006-09-05 06:17:01 +00001096}
1097
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001098void
1099DesktopWindow::setCursor(int w, int h, const Point& hotspot, void* data, void* mask) {
1100 hideLocalCursor();
1101
1102 cursor.hotspot = hotspot;
1103
1104 cursor.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001105 cursor.setPF(buffer->getScaledPixelFormat());
george824880eec2007-03-19 10:55:13 +00001106
1107 // Convert the current cursor pixel format to bpp32 if scaling mode is on.
1108 // It need because ScaledDIBSection buffer always works with bpp32 pixel data
1109 // in scaling mode.
1110 if (buffer->isScaling()) {
1111 U8 *ptr = (U8*)cursor.data;
1112 U8 *dataPtr = (U8*)data;
1113 U32 pixel = 0;
1114 int bytesPerPixel = buffer->getPixelFormat().bpp / 8;
1115 int pixelCount = w * h;
1116 PixelFormat pf = buffer->getPixelFormat();
1117
1118 while (pixelCount--) {
1119 if (bytesPerPixel == 1) {
1120 pixel = *dataPtr++;
1121 } else if (bytesPerPixel == 2) {
1122 int b0 = *dataPtr++; int b1 = *dataPtr++;
1123 pixel = b1 << 8 | b0;
1124 } else if (bytesPerPixel == 4) {
1125 int b0 = *dataPtr++; int b1 = *dataPtr++;
1126 int b2 = *dataPtr++; int b3 = *dataPtr++;
1127 pixel = b3 << 24 | b2 << 16 | b1 << 8 | b0;
1128 } else {
1129 pixel = 0;
1130 }
1131 *ptr++ = (U8)((((pixel >> pf.blueShift ) & pf.blueMax ) * 255 + pf.blueMax /2) / pf.blueMax);
1132 *ptr++ = (U8)((((pixel >> pf.greenShift) & pf.greenMax) * 255 + pf.greenMax/2) / pf.greenMax);
1133 *ptr++ = (U8)((((pixel >> pf.redShift ) & pf.redMax ) * 255 + pf.redMax /2) / pf.redMax);
1134 *ptr++ = (U8)0;
1135 }
1136 } else {
1137 cursor.imageRect(cursor.getRect(), data);
1138 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001139 memcpy(cursor.mask.buf, mask, cursor.maskLen());
1140 cursor.crop();
1141
1142 cursorBacking.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001143 cursorBacking.setPF(buffer->getScaledPixelFormat());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001144
1145 cursorAvailable = true;
1146
1147 showLocalCursor();
george824880eec2007-03-19 10:55:13 +00001148
1149 // Save the cursor parameters
1150 if (!internalSetCursor) {
1151 if (cursorImage) delete [] cursorImage;
1152 if (cursorMask) delete [] cursorMask;
1153 int cursorImageSize = (buffer->getPixelFormat().bpp/8) * w * h;
1154 cursorImage = new U8[cursorImageSize];
1155 cursorMask = new U8[cursor.maskLen()];
1156 memcpy(cursorImage, data, cursorImageSize);
1157 memcpy(cursorMask, mask, cursor.maskLen());
1158 cursorWidth = w;
1159 cursorHeight = h;
1160 cursorHotspot = hotspot;
1161 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001162}
1163
1164PixelFormat
1165DesktopWindow::getNativePF() const {
1166 vlog.debug("getNativePF()");
1167 return WindowDC(handle).getPF();
1168}
1169
1170
1171void
1172DesktopWindow::refreshWindowPalette(int start, int count) {
1173 vlog.debug("refreshWindowPalette(%d, %d)", start, count);
1174
1175 Colour colours[256];
1176 if (count > 256) {
1177 vlog.debug("%d palette entries", count);
1178 throw rdr::Exception("too many palette entries");
1179 }
1180
1181 // Copy the palette from the DIBSectionBuffer
1182 ColourMap* cm = buffer->getColourMap();
1183 if (!cm) return;
1184 for (int i=0; i<count; i++) {
1185 int r, g, b;
1186 cm->lookup(i, &r, &g, &b);
1187 colours[i].r = r;
1188 colours[i].g = g;
1189 colours[i].b = b;
1190 }
1191
1192 // Set the window palette
1193 windowPalette.setEntries(start, count, colours);
1194
1195 // Cause the window to be redrawn
1196 palette_changed = true;
1197 InvalidateRect(handle, 0, FALSE);
1198}
1199
1200
1201void DesktopWindow::calculateScrollBars() {
1202 // Calculate the required size of window
1203 DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE);
1204 DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL);
1205 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1206 DWORD old_style;
1207 RECT r;
1208 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1209 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1210 Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom);
1211
1212 if (!bumpScroll) {
1213 // We only enable scrollbars if bump-scrolling is not active.
1214 // Effectively, this means if full-screen is not active,
1215 // but I think it's better to make these things explicit.
1216
1217 // Work out whether scroll bars are required
1218 do {
1219 old_style = style;
1220
1221 if (!(style & WS_HSCROLL) && (reqd_size.width() > window_size.width())) {
1222 style |= WS_HSCROLL;
1223 reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL);
1224 }
1225 if (!(style & WS_VSCROLL) && (reqd_size.height() > window_size.height())) {
1226 style |= WS_VSCROLL;
1227 reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL);
1228 }
1229 } while (style != old_style);
1230 }
1231
1232 // Tell Windows to update the window style & cached settings
1233 if (style != current_style) {
1234 SetWindowLong(frameHandle, GWL_STYLE, style);
1235 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1236 }
1237
1238 // Update the scroll settings
1239 SCROLLINFO si;
1240 if (style & WS_VSCROLL) {
1241 si.cbSize = sizeof(si);
1242 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1243 si.nMin = 0;
1244 si.nMax = buffer->height();
1245 si.nPage = buffer->height() - (reqd_size.height() - window_size.height());
Peter Åstrand1cd3dda2008-12-09 11:17:28 +00001246 maxscrolloffset.y = __rfbmax(0, si.nMax-si.nPage);
1247 scrolloffset.y = __rfbmin(maxscrolloffset.y, scrolloffset.y);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001248 si.nPos = scrolloffset.y;
1249 SetScrollInfo(frameHandle, SB_VERT, &si, TRUE);
1250 }
1251 if (style & WS_HSCROLL) {
1252 si.cbSize = sizeof(si);
1253 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1254 si.nMin = 0;
1255 si.nMax = buffer->width();
1256 si.nPage = buffer->width() - (reqd_size.width() - window_size.width());
Peter Åstrand1cd3dda2008-12-09 11:17:28 +00001257 maxscrolloffset.x = __rfbmax(0, si.nMax-si.nPage);
1258 scrolloffset.x = __rfbmin(maxscrolloffset.x, scrolloffset.x);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001259 si.nPos = scrolloffset.x;
1260 SetScrollInfo(frameHandle, SB_HORZ, &si, TRUE);
1261 }
1262
1263 // Update the cached client size
1264 GetClientRect(frameHandle, &r);
1265 client_size = Rect(r.left, r.top, r.right, r.bottom);
1266}
1267
george82858a4642007-01-27 15:32:27 +00001268void DesktopWindow::resizeDesktopWindowToBuffer() {
1269 RECT r;
1270 DWORD style = GetWindowLong(frameHandle, GWL_STYLE) & ~(WS_VSCROLL | WS_HSCROLL);
1271 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1272
1273 // Calculate the required size of the desktop window
1274 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1275 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1276 if (isToolbarEnabled())
1277 r.bottom += tb.getHeight();
1278 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
1279
1280 // Set the required size, center the main window and clip to the current monitor
1281 SetWindowPos(handle, 0, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
1282 centerWindow(handle, NULL);
1283 MonitorInfo mi(getMonitor());
1284 mi.clipTo(handle);
1285
1286 // Enable/disable scrollbars as appropriate
1287 calculateScrollBars();
1288}
1289
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001290
Pierre Ossmanb2ff1602009-03-25 12:13:28 +00001291void DesktopWindow::framebufferUpdateEnd()
1292{
1293 updateWindow();
1294}
1295
1296
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001297void
1298DesktopWindow::setName(const char* name) {
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001299 if (name != desktopName) {
1300 strCopy(desktopName, name, sizeof(desktopName));
1301 }
1302 char *newTitle = new char[strlen(desktopName)+20];
Peter Åstrand4eacc022009-02-27 10:12:14 +00001303 sprintf(newTitle, "TigerVNC: %.240s @ %i%%", desktopName, getDesktopScale());
Peter Åstrand7f0189d2009-01-27 14:36:36 +00001304 SetWindowText(handle, TStr(newTitle));
1305 delete [] newTitle;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001306}
1307
1308
1309void
Adam Tkacacf6c6b2009-02-13 12:42:05 +00001310DesktopWindow::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001311 CharArray t(len+1);
1312 memcpy(t.buf, str, len);
1313 t.buf[len] = 0;
1314 clipboard.setClipText(t.buf);
1315}
1316
1317
1318void DesktopWindow::fillRect(const Rect& r, Pixel pix) {
1319 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1320 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1321 buffer->fillRect(r, pix);
1322 invalidateDesktopRect(r);
1323}
1324void DesktopWindow::imageRect(const Rect& r, void* pixels) {
1325 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1326 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1327 buffer->imageRect(r, pixels);
1328 invalidateDesktopRect(r);
1329}
1330void DesktopWindow::copyRect(const Rect& r, int srcX, int srcY) {
1331 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1332 if (cursorBackingRect.overlaps(img_rect) ||
1333 cursorBackingRect.overlaps(Rect(srcX, srcY, srcX+img_rect.width(), srcY+img_rect.height())))
1334 hideLocalCursor();
1335 buffer->copyRect(r, Point(r.tl.x-srcX, r.tl.y-srcY));
1336 invalidateDesktopRect(r);
1337}
1338
1339void DesktopWindow::invertRect(const Rect& r) {
1340 int stride;
1341 rdr::U8* p = buffer->isScaling() ? buffer->getPixelsRW(buffer->calculateScaleBoundary(r), &stride)
1342 : buffer->getPixelsRW(r, &stride);
1343 for (int y = 0; y < r.height(); y++) {
1344 for (int x = 0; x < r.width(); x++) {
1345 switch (buffer->getPF().bpp) {
1346 case 8: ((rdr::U8* )p)[x+y*stride] ^= 0xff; break;
1347 case 16: ((rdr::U16*)p)[x+y*stride] ^= 0xffff; break;
1348 case 32: ((rdr::U32*)p)[x+y*stride] ^= 0xffffffff; break;
1349 }
1350 }
1351 }
1352 invalidateDesktopRect(r);
1353}