blob: b0bebaf1e772be3910e3de34cc87a2d38ce28611 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19#include <windows.h>
20#include <commctrl.h>
george82770bbbc2007-03-12 10:48:09 +000021#include <math.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000022#include <rfb/Configuration.h>
23#include <rfb/LogWriter.h>
24#include <rfb_win32/WMShatter.h>
25#include <rfb_win32/LowLevelKeyEvents.h>
26#include <rfb_win32/MonitorInfo.h>
27#include <rfb_win32/DeviceContext.h>
28#include <rfb_win32/Win32Util.h>
george82fd334ad2006-05-29 14:05:20 +000029#include <rfb_win32/MsgBox.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000030#include <vncviewer/DesktopWindow.h>
31#include <vncviewer/resource.h>
32
33using namespace rfb;
34using namespace rfb::win32;
35
36
37// - Statics & consts
38
39static LogWriter vlog("DesktopWindow");
40
41const int TIMER_BUMPSCROLL = 1;
42const int TIMER_POINTER_INTERVAL = 2;
43const int TIMER_POINTER_3BUTTON = 3;
44
45
46//
47// -=- DesktopWindowClass
48
49//
50// Window class used as the basis for all DesktopWindow instances
51//
52
53class DesktopWindowClass {
54public:
55 DesktopWindowClass();
56 ~DesktopWindowClass();
57 ATOM classAtom;
58 HINSTANCE instance;
59};
60
61LRESULT CALLBACK DesktopWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
62 LRESULT result;
63 if (msg == WM_CREATE)
64 SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
65 else if (msg == WM_DESTROY)
66 SetWindowLong(wnd, GWL_USERDATA, 0);
67 DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA);
68 if (!_this) {
69 vlog.info("null _this in %x, message %u", wnd, msg);
70 return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam);
71 }
72
73 try {
74 result = _this->processMessage(msg, wParam, lParam);
george82fd334ad2006-05-29 14:05:20 +000075 } catch (rfb::UnsupportedPixelFormatException &e) {
76 MsgBox(0, e.str(), MB_OK);
77 _this->getCallback()->closeWindow();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000078 } catch (rdr::Exception& e) {
79 vlog.error("untrapped: %s", e.str());
80 }
81
82 return result;
83};
84
85static HCURSOR dotCursor = (HCURSOR)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDC_DOT_CURSOR), IMAGE_CURSOR, 0, 0, LR_SHARED);
86static HCURSOR arrowCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
87
88DesktopWindowClass::DesktopWindowClass() : classAtom(0) {
89 WNDCLASS wndClass;
90 wndClass.style = 0;
91 wndClass.lpfnWndProc = DesktopWindowProc;
92 wndClass.cbClsExtra = 0;
93 wndClass.cbWndExtra = 0;
94 wndClass.hInstance = instance = GetModuleHandle(0);
95 wndClass.hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 0, 0, LR_SHARED);
96 if (!wndClass.hIcon)
97 printf("unable to load icon:%ld", GetLastError());
98 wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
99 wndClass.hbrBackground = NULL;
100 wndClass.lpszMenuName = 0;
101 wndClass.lpszClassName = _T("rfb::win32::DesktopWindowClass");
102 classAtom = RegisterClass(&wndClass);
103 if (!classAtom) {
104 throw rdr::SystemException("unable to register DesktopWindow window class", GetLastError());
105 }
106}
107
108DesktopWindowClass::~DesktopWindowClass() {
109 if (classAtom) {
110 UnregisterClass((const TCHAR*)classAtom, instance);
111 }
112}
113
114DesktopWindowClass baseClass;
115
116//
117// -=- FrameClass
118
119//
120// Window class used for child windows that display pixel data
121//
122
123class FrameClass {
124public:
125 FrameClass();
126 ~FrameClass();
127 ATOM classAtom;
128 HINSTANCE instance;
129};
130
131LRESULT CALLBACK FrameProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
132 LRESULT result;
133 if (msg == WM_CREATE)
134 SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
135 else if (msg == WM_DESTROY)
136 SetWindowLong(wnd, GWL_USERDATA, 0);
137 DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA);
138 if (!_this) {
139 vlog.info("null _this in %x, message %u", wnd, msg);
140 return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam);
141 }
142
143 try {
144 result = _this->processFrameMessage(msg, wParam, lParam);
145 } catch (rdr::Exception& e) {
146 vlog.error("untrapped: %s", e.str());
147 }
148
149 return result;
150}
151
152FrameClass::FrameClass() : classAtom(0) {
153 WNDCLASS wndClass;
154 wndClass.style = 0;
155 wndClass.lpfnWndProc = FrameProc;
156 wndClass.cbClsExtra = 0;
157 wndClass.cbWndExtra = 0;
158 wndClass.hInstance = instance = GetModuleHandle(0);
159 wndClass.hIcon = 0;
160 wndClass.hCursor = NULL;
161 wndClass.hbrBackground = NULL;
162 wndClass.lpszMenuName = 0;
163 wndClass.lpszClassName = _T("rfb::win32::FrameClass");
164 classAtom = RegisterClass(&wndClass);
165 if (!classAtom) {
166 throw rdr::SystemException("unable to register Frame window class", GetLastError());
167 }
168}
169
170FrameClass::~FrameClass() {
171 if (classAtom) {
172 UnregisterClass((const TCHAR*)classAtom, instance);
173 }
174}
175
176FrameClass frameClass;
177
178
179//
180// -=- DesktopWindow instance implementation
181//
182
183DesktopWindow::DesktopWindow(Callback* cb)
184 : buffer(0),
george82ffc14a62006-09-05 06:51:41 +0000185 showToolbar(false), autoScaling(false),
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000186 client_size(0, 0, 16, 16), window_size(0, 0, 32, 32),
187 cursorVisible(false), cursorAvailable(false), cursorInBuffer(false),
188 systemCursorVisible(true), trackingMouseLeave(false),
189 handle(0), frameHandle(0), has_focus(false), palette_changed(false),
190 fullscreenActive(false), fullscreenRestore(false),
191 bumpScroll(false), callback(cb) {
192
193 // Create the window
194 const char* name = "DesktopWindow";
195 handle = CreateWindow((const TCHAR*)baseClass.classAtom, TStr(name),
196 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
197 0, 0, 10, 10, 0, 0, baseClass.instance, this);
198 if (!handle)
199 throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError());
200 vlog.debug("created window \"%s\" (%x)", name, handle);
201
202 // Create the toolbar
203 tb.create(handle);
204 vlog.debug("created toolbar window \"%s\" (%x)", "ViewerToolBar", tb.getHandle());
205
206 // Create the frame window
207 frameHandle = CreateWindowEx(WS_EX_CLIENTEDGE, (const TCHAR*)frameClass.classAtom,
208 0, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
209 CW_USEDEFAULT, CW_USEDEFAULT, handle, 0, frameClass.instance, this);
210 if (!frameHandle) {
211 throw rdr::SystemException("unable to create rfb frame window instance", GetLastError());
212 }
213 vlog.debug("created window \"%s\" (%x)", "Frame Window", frameHandle);
214
215 // Initialise the CPointer pointer handler
216 ptr.setHWND(frameHandle);
217 ptr.setIntervalTimerId(TIMER_POINTER_INTERVAL);
218 ptr.set3ButtonTimerId(TIMER_POINTER_3BUTTON);
219
220 // Initialise the bumpscroll timer
221 bumpScrollTimer.setHWND(handle);
222 bumpScrollTimer.setId(TIMER_BUMPSCROLL);
223
224 // Hook the clipboard
225 clipboard.setNotifier(this);
226
227 // Create the backing buffer
228 buffer = new win32::ScaledDIBSectionBuffer(frameHandle);
229
230 // Show the window
231 centerWindow(handle, 0);
232 ShowWindow(handle, SW_SHOW);
233}
234
235DesktopWindow::~DesktopWindow() {
236 vlog.debug("~DesktopWindow");
237 showSystemCursor();
238 if (handle) {
239 disableLowLevelKeyEvents(handle);
240 DestroyWindow(handle);
241 handle = 0;
242 }
243 delete buffer;
244 vlog.debug("~DesktopWindow done");
245}
246
247
248void DesktopWindow::setFullscreen(bool fs) {
249 if (fs && !fullscreenActive) {
250 fullscreenActive = bumpScroll = true;
251
252 // Un-minimize the window if required
253 if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE)
254 ShowWindow(handle, SW_RESTORE);
255
256 // Save the current window position
257 GetWindowRect(handle, &fullscreenOldRect);
258
259 // Find the size of the display the window is on
260 MonitorInfo mi(handle);
261
262 // Hide the toolbar
263 if (tb.isVisible())
264 tb.hide();
265 SetWindowLong(frameHandle, GWL_EXSTYLE, 0);
266
267 // Set the window full-screen
268 DWORD flags = GetWindowLong(handle, GWL_STYLE);
269 fullscreenOldFlags = flags;
270 flags = flags & ~(WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZE | WS_MINIMIZE);
271 vlog.debug("flags=%x", flags);
272
273 SetWindowLong(handle, GWL_STYLE, flags);
george82ced9db12006-09-11 09:09:14 +0000274 SetWindowPos(handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000275 mi.rcMonitor.right-mi.rcMonitor.left,
276 mi.rcMonitor.bottom-mi.rcMonitor.top,
277 SWP_FRAMECHANGED);
278 } else if (!fs && fullscreenActive) {
279 fullscreenActive = bumpScroll = false;
280
281 // Show the toolbar
282 if (showToolbar)
283 tb.show();
284 SetWindowLong(frameHandle, GWL_EXSTYLE, WS_EX_CLIENTEDGE);
285
286 // Set the window non-fullscreen
287 SetWindowLong(handle, GWL_STYLE, fullscreenOldFlags);
288
289 // Set the window position
290 SetWindowPos(handle, HWND_NOTOPMOST,
291 fullscreenOldRect.left, fullscreenOldRect.top,
292 fullscreenOldRect.right - fullscreenOldRect.left,
293 fullscreenOldRect.bottom - fullscreenOldRect.top,
294 SWP_FRAMECHANGED);
295 }
296
297 // Adjust the viewport offset to cope with change in size between FS
298 // and previous window state.
299 setViewportOffset(scrolloffset);
300}
301
302void DesktopWindow::setShowToolbar(bool st)
303{
304 showToolbar = st;
george82be3e9692006-06-10 12:58:41 +0000305 if (fullscreenActive) return;
306
george8222856792006-06-10 11:47:22 +0000307 RECT r;
308 GetWindowRect(handle, &r);
309 bool maximized = GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000310
george8222856792006-06-10 11:47:22 +0000311 if (showToolbar && !tb.isVisible()) {
george8274ea5f32006-09-11 11:40:12 +0000312 refreshToolbarButtons();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000313 tb.show();
george8222856792006-06-10 11:47:22 +0000314 if (!maximized) r.bottom += tb.getHeight();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000315 } else if (!showToolbar && tb.isVisible()) {
316 tb.hide();
george8222856792006-06-10 11:47:22 +0000317 if (!maximized) r.bottom -= tb.getHeight();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000318 }
george8222856792006-06-10 11:47:22 +0000319 // Resize the chiled windows even if the parent window size
320 // has not been changed (the main window is maximized)
321 if (maximized) SendMessage(handle, WM_SIZE, 0, 0);
322 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 +0000323}
324
george8274ea5f32006-09-11 11:40:12 +0000325void DesktopWindow::refreshToolbarButtons() {
326 int scale = getDesktopScale();
george8274ea5f32006-09-11 11:40:12 +0000327 if (scale <= 10) {
328 tb.enableButton(ID_ZOOM_IN, true);
329 tb.enableButton(ID_ZOOM_OUT, false);
330 } else if (scale >= 200) {
331 tb.enableButton(ID_ZOOM_IN, false);
332 tb.enableButton(ID_ZOOM_OUT, true);
333 } else {
334 tb.enableButton(ID_ZOOM_IN, true);
335 tb.enableButton(ID_ZOOM_OUT, true);
336 }
george824a185b02007-03-12 14:26:33 +0000337 if (buffer->isScaling() || isAutoScaling()) tb.enableButton(ID_ACTUAL_SIZE, true);
338 else tb.enableButton(ID_ACTUAL_SIZE, false);
george8274ea5f32006-09-11 11:40:12 +0000339 if (isAutoScaling()) tb.pressButton(ID_AUTO_SIZE, true);
340 else tb.pressButton(ID_AUTO_SIZE, false);
341}
342
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000343void DesktopWindow::setDisableWinKeys(bool dwk) {
344 // Enable low-level event hooking, so we get special keys directly
345 if (dwk)
346 enableLowLevelKeyEvents(handle);
347 else
348 disableLowLevelKeyEvents(handle);
349}
350
351
352void DesktopWindow::setMonitor(const char* monitor) {
353 MonitorInfo mi(monitor);
354 mi.moveTo(handle);
355}
356
357char* DesktopWindow::getMonitor() const {
358 MonitorInfo mi(handle);
359 return strDup(mi.szDevice);
360}
361
362
363bool DesktopWindow::setViewportOffset(const Point& tl) {
364 Point np = Point(max(0, min(tl.x, buffer->width()-client_size.width())),
365 max(0, min(tl.y, buffer->height()-client_size.height())));
366 Point delta = np.translate(scrolloffset.negate());
367 if (!np.equals(scrolloffset)) {
368 scrolloffset = np;
369 ScrollWindowEx(frameHandle, -delta.x, -delta.y, 0, 0, 0, 0, SW_INVALIDATE);
370 UpdateWindow(frameHandle);
371 return true;
372 }
373 return false;
374}
375
376
377bool DesktopWindow::processBumpScroll(const Point& pos)
378{
379 if (!bumpScroll) return false;
380 int bumpScrollPixels = 20;
381 bumpScrollDelta = Point();
382
383 if (pos.x == client_size.width()-1)
384 bumpScrollDelta.x = bumpScrollPixels;
385 else if (pos.x == 0)
386 bumpScrollDelta.x = -bumpScrollPixels;
387 if (pos.y == client_size.height()-1)
388 bumpScrollDelta.y = bumpScrollPixels;
389 else if (pos.y == 0)
390 bumpScrollDelta.y = -bumpScrollPixels;
391
392 if (bumpScrollDelta.x || bumpScrollDelta.y) {
393 if (bumpScrollTimer.isActive()) return true;
394 if (setViewportOffset(scrolloffset.translate(bumpScrollDelta))) {
395 bumpScrollTimer.start(25);
396 return true;
397 }
398 }
399
400 bumpScrollTimer.stop();
401 return false;
402}
403
404
405LRESULT
406DesktopWindow::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
407 switch (msg) {
408
409 // -=- Process standard window messages
410
411 case WM_NOTIFY:
412 if (wParam == ID_TOOLBAR)
413 tb.processWM_NOTIFY(wParam, lParam);
414 break;
415
416 case WM_DISPLAYCHANGE:
417 // Display format has changed - notify callback
418 callback->displayChanged();
419 break;
420
421 // -=- Window position
422
423 // Prevent the window from being resized to be too large if in normal mode.
424 // If maximized or fullscreen the allow oversized windows.
425
426 case WM_WINDOWPOSCHANGING:
427 {
428 WINDOWPOS* wpos = (WINDOWPOS*)lParam;
george82ffc14a62006-09-05 06:51:41 +0000429 if ((wpos->flags & SWP_NOSIZE) || isAutoScaling())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000430 break;
431
george82f0775132007-01-27 15:48:25 +0000432 // Calculate the minimum size of main window
433 RECT r;
434 Rect min_size;
435 int tbMinWidth = 0, tbMinHeight = 0;
436 if (isToolbarEnabled()) {
437 tbMinWidth = tb.getTotalWidth();
438 tbMinHeight = tb.getHeight();
439 SetRect(&r, 0, 0, tbMinWidth, tbMinHeight);
440 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
441 min_size = Rect(r.left, r.top, r.right, r.bottom);
442 }
443
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000444 // Work out how big the window should ideally be
445 DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE);
446 DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL);
447 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
448
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000449 SetRect(&r, 0, 0, buffer->width(), buffer->height());
450 AdjustWindowRectEx(&r, style, FALSE, style_ex);
451 Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom);
452 if (current_style & WS_VSCROLL)
453 reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL);
454 if (current_style & WS_HSCROLL)
455 reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL);
456
457 SetRect(&r, reqd_size.tl.x, reqd_size.tl.y, reqd_size.br.x, reqd_size.br.y);
george82ced9db12006-09-11 09:09:14 +0000458 if (isToolbarEnabled())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000459 r.bottom += tb.getHeight();
460 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
461 reqd_size = Rect(r.left, r.top, r.right, r.bottom);
462
463 RECT current;
464 GetWindowRect(handle, &current);
465
george82f0775132007-01-27 15:48:25 +0000466 if (min_size.width() > reqd_size.width()) {
467 reqd_size.tl.x = min_size.tl.x;
468 reqd_size.br.x = min_size.br.x;
469 }
470 if (min_size.height() > reqd_size.height()) {
471 reqd_size.tl.y = min_size.tl.y;
472 reqd_size.br.y = min_size.br.y;
473 }
474
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000475 if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) {
george82f0775132007-01-27 15:48:25 +0000476 // Ensure that the window isn't resized too large or too small
477 if ((wpos->cx < min_size.width()) && isToolbarEnabled()) {
478 wpos->cx = min_size.width();
479 wpos->x = current.left;
480 } else if ((wpos->cx > reqd_size.width())) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000481 wpos->cx = reqd_size.width();
482 wpos->x = current.left;
483 }
george82f0775132007-01-27 15:48:25 +0000484 if ((wpos->cy < min_size.height()) && isToolbarEnabled()) {
485 wpos->cy = min_size.height();
486 wpos->y = current.top;
487 } else if (wpos->cy > reqd_size.height()) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000488 wpos->cy = reqd_size.height();
489 wpos->y = current.top;
490 }
491 }
492 }
493 break;
494
495 // Resize child windows and update window size info we have cached.
496
497 case WM_SIZE:
498 {
499 Point old_offset = desktopToClient(Point(0, 0));
500 RECT r;
501
502 // Resize child windows
503 GetClientRect(handle, &r);
504 if (tb.isVisible()) {
george82858a4642007-01-27 15:32:27 +0000505 MoveWindow(frameHandle, 0, tb.getHeight(), r.right, r.bottom - tb.getHeight(), TRUE);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000506 } else {
507 MoveWindow(frameHandle, 0, 0, r.right, r.bottom, TRUE);
508 }
509 tb.autoSize();
510
511 // Update the cached sizing information
512 GetWindowRect(frameHandle, &r);
513 window_size = Rect(r.left, r.top, r.right, r.bottom);
514 GetClientRect(frameHandle, &r);
515 client_size = Rect(r.left, r.top, r.right, r.bottom);
516
george82ffc14a62006-09-05 06:51:41 +0000517 // Perform the AutoScaling operation
518 if (isAutoScaling()) {
519 fitBufferToWindow(false);
520 } else {
521 // Determine whether scrollbars are required
522 calculateScrollBars();
523 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000524
525 // Redraw if required
george82ffc14a62006-09-05 06:51:41 +0000526 if ((!old_offset.equals(desktopToClient(Point(0, 0)))) || isAutoScaling())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000527 InvalidateRect(frameHandle, 0, TRUE);
528 }
529 break;
530
531 // -=- Bump-scrolling
532
533 case WM_TIMER:
534 switch (wParam) {
535 case TIMER_BUMPSCROLL:
536 if (!setViewportOffset(scrolloffset.translate(bumpScrollDelta)))
537 bumpScrollTimer.stop();
538 break;
539 case TIMER_POINTER_INTERVAL:
540 case TIMER_POINTER_3BUTTON:
541 ptr.handleTimer(callback, wParam);
542 break;
543 }
544 break;
545
546 // -=- Track whether or not the window has focus
547
548 case WM_SETFOCUS:
549 has_focus = true;
550 break;
551 case WM_KILLFOCUS:
552 has_focus = false;
553 cursorOutsideBuffer();
554 // Restore the keyboard to a consistent state
555 kbd.releaseAllKeys(callback);
556 break;
557
558 // -=- If the menu is about to be shown, make sure it's up to date
559
560 case WM_INITMENU:
561 callback->refreshMenu(true);
562 break;
563
564 // -=- Handle the extra window menu items
565
566 // Pass system menu messages to the callback and only attempt
567 // to process them ourselves if the callback returns false.
568 case WM_SYSCOMMAND:
569 // Call the supplied callback
570 if (callback->sysCommand(wParam, lParam))
571 break;
572
573 // - Not processed by the callback, so process it as a system message
574 switch (wParam & 0xfff0) {
575
576 // When restored, ensure that full-screen mode is re-enabled if required.
577 case SC_RESTORE:
578 {
579 if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE) {
580 rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
581 setFullscreen(fullscreenRestore);
582 }
583 else if (fullscreenActive)
584 setFullscreen(false);
585 else
586 rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
587
588 return 0;
589 }
590
591 // If we are maximized or minimized then that cancels full-screen mode.
592 case SC_MINIMIZE:
593 case SC_MAXIMIZE:
594 fullscreenRestore = fullscreenActive;
595 setFullscreen(false);
596 break;
597
598 }
599 break;
600
601 // Treat all menu commands as system menu commands
602 case WM_COMMAND:
603 SendMessage(handle, WM_SYSCOMMAND, wParam, lParam);
604 return 0;
605
606 // -=- Handle keyboard input
607
608 case WM_KEYUP:
609 case WM_KEYDOWN:
610 // Hook the MenuKey to pop-up the window menu
611 if (menuKey && (wParam == menuKey)) {
612
613 bool ctrlDown = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
614 bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0;
615 bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
616 if (!(ctrlDown || altDown || shiftDown)) {
617
618 // If MenuKey is being released then pop-up the menu
619 if ((msg == WM_KEYDOWN)) {
620 // Make sure it's up to date
621 //
622 // NOTE: Here we call refreshMenu only to grey out Move and Size
623 // menu items. Other things will be refreshed once again
624 // while processing the WM_INITMENU message.
625 //
626 callback->refreshMenu(false);
627
628 // Show it under the pointer
629 POINT pt;
630 GetCursorPos(&pt);
631 cursorInBuffer = false;
632 TrackPopupMenu(GetSystemMenu(handle, FALSE),
633 TPM_CENTERALIGN | TPM_VCENTERALIGN, pt.x, pt.y, 0, handle, 0);
634 }
635
636 // Ignore the MenuKey keypress for both press & release events
637 return 0;
638 }
639 }
640 case WM_SYSKEYDOWN:
641 case WM_SYSKEYUP:
642 kbd.keyEvent(callback, wParam, lParam, (msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN));
643 return 0;
644
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000645 // -=- Handle mouse wheel scroll events
646
647#ifdef WM_MOUSEWHEEL
648 case WM_MOUSEWHEEL:
649 processMouseMessage(msg, wParam, lParam);
650 break;
651#endif
652
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000653 // -=- Handle the window closing
654
655 case WM_CLOSE:
656 vlog.debug("WM_CLOSE %x", handle);
657 callback->closeWindow();
658 break;
659
660 }
661
662 return rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam);
663}
664
665LRESULT
666DesktopWindow::processFrameMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
667 switch (msg) {
668
669 // -=- Paint the remote frame buffer
670
671 case WM_PAINT:
672 {
673 PAINTSTRUCT ps;
674 HDC paintDC = BeginPaint(frameHandle, &ps);
675 if (!paintDC)
676 throw rdr::SystemException("unable to BeginPaint", GetLastError());
677 Rect pr = Rect(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom);
678
679 if (!pr.is_empty()) {
680
681 // Draw using the correct palette
682 PaletteSelector pSel(paintDC, windowPalette.getHandle());
683
684 if (buffer->bitmap) {
685 // Update the bitmap's palette
686 if (palette_changed) {
687 palette_changed = false;
688 buffer->refreshPalette();
689 }
690
691 // Get device context
692 BitmapDC bitmapDC(paintDC, buffer->bitmap);
693
694 // Blit the border if required
695 Rect bufpos = desktopToClient(buffer->getRect());
696 if (!pr.enclosed_by(bufpos)) {
697 vlog.debug("draw border");
698 HBRUSH black = (HBRUSH) GetStockObject(BLACK_BRUSH);
699 RECT r;
700 SetRect(&r, 0, 0, bufpos.tl.x, client_size.height()); FillRect(paintDC, &r, black);
701 SetRect(&r, bufpos.tl.x, 0, bufpos.br.x, bufpos.tl.y); FillRect(paintDC, &r, black);
702 SetRect(&r, bufpos.br.x, 0, client_size.width(), client_size.height()); FillRect(paintDC, &r, black);
703 SetRect(&r, bufpos.tl.x, bufpos.br.y, bufpos.br.x, client_size.height()); FillRect(paintDC, &r, black);
704 }
705
706 // Do the blit
707 Point buf_pos = clientToDesktop(pr.tl);
708
709 if (!BitBlt(paintDC, pr.tl.x, pr.tl.y, pr.width(), pr.height(),
710 bitmapDC, buf_pos.x, buf_pos.y, SRCCOPY))
711 throw rdr::SystemException("unable to BitBlt to window", GetLastError());
712 }
713 }
714
715 EndPaint(frameHandle, &ps);
716
717 // - Notify the callback that a paint message has finished processing
718 callback->paintCompleted();
719 }
720 return 0;
721
722 // -=- Palette management
723
724 case WM_PALETTECHANGED:
725 vlog.debug("WM_PALETTECHANGED");
726 if ((HWND)wParam == frameHandle) {
727 vlog.debug("ignoring");
728 break;
729 }
730 case WM_QUERYNEWPALETTE:
731 vlog.debug("re-selecting palette");
732 {
733 WindowDC wdc(frameHandle);
734 PaletteSelector pSel(wdc, windowPalette.getHandle());
735 if (pSel.isRedrawRequired()) {
736 InvalidateRect(frameHandle, 0, FALSE);
737 UpdateWindow(frameHandle);
738 }
739 }
740 return TRUE;
741
742 case WM_VSCROLL:
743 case WM_HSCROLL:
744 {
745 Point delta;
746 int newpos = (msg == WM_VSCROLL) ? scrolloffset.y : scrolloffset.x;
747
748 switch (LOWORD(wParam)) {
749 case SB_PAGEUP: newpos -= 50; break;
750 case SB_PAGEDOWN: newpos += 50; break;
751 case SB_LINEUP: newpos -= 5; break;
752 case SB_LINEDOWN: newpos += 5; break;
753 case SB_THUMBTRACK:
754 case SB_THUMBPOSITION: newpos = HIWORD(wParam); break;
755 default: vlog.info("received unknown scroll message");
756 };
757
758 if (msg == WM_HSCROLL)
759 setViewportOffset(Point(newpos, scrolloffset.y));
760 else
761 setViewportOffset(Point(scrolloffset.x, newpos));
762
763 SCROLLINFO si;
764 si.cbSize = sizeof(si);
765 si.fMask = SIF_POS;
766 si.nPos = newpos;
767 SetScrollInfo(frameHandle, (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ, &si, TRUE);
768 }
769 break;
770
771 // -=- Cursor shape/visibility handling
772
773 case WM_SETCURSOR:
774 if (LOWORD(lParam) != HTCLIENT)
775 break;
776 SetCursor(cursorInBuffer ? dotCursor : arrowCursor);
777 return TRUE;
778
779 case WM_MOUSELEAVE:
780 trackingMouseLeave = false;
781 cursorOutsideBuffer();
782 return 0;
783
784 // -=- Mouse input handling
785
786 case WM_MOUSEMOVE:
787 case WM_LBUTTONUP:
788 case WM_MBUTTONUP:
789 case WM_RBUTTONUP:
790 case WM_LBUTTONDOWN:
791 case WM_MBUTTONDOWN:
792 case WM_RBUTTONDOWN:
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000793 processMouseMessage(msg, wParam, lParam);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000794 break;
795 }
796
797 return rfb::win32::SafeDefWindowProc(frameHandle, msg, wParam, lParam);
798}
799
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000800void
801DesktopWindow::processMouseMessage(UINT msg, WPARAM wParam, LPARAM lParam)
802{
803 if (!has_focus) {
804 cursorOutsideBuffer();
805 return;
806 }
807
808 if (!trackingMouseLeave) {
809 TRACKMOUSEEVENT tme;
810 tme.cbSize = sizeof(TRACKMOUSEEVENT);
811 tme.dwFlags = TME_LEAVE;
812 tme.hwndTrack = frameHandle;
813 _TrackMouseEvent(&tme);
814 trackingMouseLeave = true;
815 }
816 int mask = 0;
817 if (LOWORD(wParam) & MK_LBUTTON) mask |= 1;
818 if (LOWORD(wParam) & MK_MBUTTON) mask |= 2;
819 if (LOWORD(wParam) & MK_RBUTTON) mask |= 4;
820
821#ifdef WM_MOUSEWHEEL
822 if (msg == WM_MOUSEWHEEL) {
823 int delta = (short)HIWORD(wParam);
824 int repeats = (abs(delta)+119) / 120;
825 int wheelMask = (delta > 0) ? 8 : 16;
826 vlog.debug("repeats %d, mask %d\n",repeats,wheelMask);
827 for (int i=0; i<repeats; i++) {
828 ptr.pointerEvent(callback, oldpos, mask | wheelMask);
829 ptr.pointerEvent(callback, oldpos, mask);
830 }
831 } else {
832#endif
833 Point clientPos = Point(LOWORD(lParam), HIWORD(lParam));
834 Point p = clientToDesktop(clientPos);
835
836 // If the mouse is not within the server buffer area, do nothing
837 cursorInBuffer = buffer->getRect().contains(p);
838 if (!cursorInBuffer) {
839 cursorOutsideBuffer();
840 return;
841 }
842
843 // If we're locally rendering the cursor then redraw it
844 if (cursorAvailable) {
845 // - Render the cursor!
846 if (!p.equals(cursorPos)) {
847 hideLocalCursor();
848 cursorPos = p;
849 showLocalCursor();
850 if (cursorVisible)
851 hideSystemCursor();
852 }
853 }
854
855 // If we are doing bump-scrolling then try that first...
856 if (processBumpScroll(clientPos))
857 return;
858
859 // Send a pointer event to the server
860 oldpos = p;
861 if (buffer->isScaling()) {
george822446ed02007-03-10 08:55:35 +0000862 p.x /= buffer->getScaleRatioX();
863 p.y /= buffer->getScaleRatioY();
Constantin Kaplinskyd5f59272006-09-14 05:14:43 +0000864 }
865 ptr.pointerEvent(callback, p, mask);
866#ifdef WM_MOUSEWHEEL
867 }
868#endif
869}
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000870
871void
872DesktopWindow::hideLocalCursor() {
873 // - Blit the cursor backing store over the cursor
874 // *** ALWAYS call this BEFORE changing buffer PF!!!
875 if (cursorVisible) {
876 cursorVisible = false;
877 buffer->DIBSectionBuffer::imageRect(cursorBackingRect, cursorBacking.data);
878 invalidateDesktopRect(cursorBackingRect, false);
879 }
880}
881
882void
883DesktopWindow::showLocalCursor() {
884 if (cursorAvailable && !cursorVisible && cursorInBuffer) {
george827c721cc2006-09-23 07:09:37 +0000885 if (!buffer->getScaledPixelFormat().equal(cursor.getPF()) ||
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000886 cursor.getRect().is_empty()) {
887 vlog.info("attempting to render invalid local cursor");
888 cursorAvailable = false;
889 showSystemCursor();
890 return;
891 }
892 cursorVisible = true;
893
894 cursorBackingRect = cursor.getRect().translate(cursorPos).translate(cursor.hotspot.negate());
895 cursorBackingRect = cursorBackingRect.intersect(buffer->getRect());
896 buffer->getImage(cursorBacking.data, cursorBackingRect);
897
898 renderLocalCursor();
899
900 invalidateDesktopRect(cursorBackingRect, false);
901 }
902}
903
904void DesktopWindow::cursorOutsideBuffer()
905{
906 cursorInBuffer = false;
907 hideLocalCursor();
908 showSystemCursor();
909}
910
911void
912DesktopWindow::renderLocalCursor()
913{
914 Rect r = cursor.getRect();
915 r = r.translate(cursorPos).translate(cursor.hotspot.negate());
916 buffer->DIBSectionBuffer::maskRect(r, cursor.data, cursor.mask.buf);
917}
918
919void
920DesktopWindow::hideSystemCursor() {
921 if (systemCursorVisible) {
922 vlog.debug("hide system cursor");
923 systemCursorVisible = false;
924 ShowCursor(FALSE);
925 }
926}
927
928void
929DesktopWindow::showSystemCursor() {
930 if (!systemCursorVisible) {
931 vlog.debug("show system cursor");
932 systemCursorVisible = true;
933 ShowCursor(TRUE);
934 }
935}
936
937
938bool
939DesktopWindow::invalidateDesktopRect(const Rect& crect, bool scaling) {
940 Rect rect;
941 if (buffer->isScaling() && scaling) {
942 rect = desktopToClient(buffer->calculateScaleBoundary(crect));
943 } else rect = desktopToClient(crect);
944 if (rect.intersect(client_size).is_empty()) return false;
945 RECT invalid = {rect.tl.x, rect.tl.y, rect.br.x, rect.br.y};
946 InvalidateRect(frameHandle, &invalid, FALSE);
947 return true;
948}
949
950
951void
952DesktopWindow::notifyClipboardChanged(const char* text, int len) {
953 callback->clientCutText(text, len);
954}
955
956
957void
958DesktopWindow::setPF(const PixelFormat& pf) {
959 // If the cursor is the wrong format then clear it
george827c721cc2006-09-23 07:09:37 +0000960 if (!pf.equal(buffer->getScaledPixelFormat()))
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000961 setCursor(0, 0, Point(), 0, 0);
962
963 // Update the desktop buffer
964 buffer->setPF(pf);
965
966 // Redraw the window
967 InvalidateRect(frameHandle, 0, FALSE);
968}
969
970void
971DesktopWindow::setSize(int w, int h) {
972 vlog.debug("setSize %dx%d", w, h);
973
974 // If the locally-rendered cursor is visible then remove it
975 hideLocalCursor();
976
977 // Resize the backing buffer
978 buffer->setSize(w, h);
979
george82ffc14a62006-09-05 06:51:41 +0000980 // Calculate the pixel buffer aspect correlation. It's used
981 // for the autoScaling operation.
982 aspect_corr = (double)w / h;
983
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000984 // If the window is not maximised or full-screen then resize it
985 if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) {
986 // Resize the window to the required size
987 RECT r = {0, 0, w, h};
988 AdjustWindowRectEx(&r, GetWindowLong(frameHandle, GWL_STYLE), FALSE,
989 GetWindowLong(frameHandle, GWL_EXSTYLE));
george82ced9db12006-09-11 09:09:14 +0000990 if (isToolbarEnabled())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000991 r.bottom += tb.getHeight();
992 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
993
994 // Resize about the center of the window, and clip to current monitor
995 MonitorInfo mi(handle);
996 resizeWindow(handle, r.right-r.left, r.bottom-r.top);
997 mi.clipTo(handle);
998 } else {
999 // Ensure the screen contents are consistent
1000 InvalidateRect(frameHandle, 0, FALSE);
1001 }
1002
1003 // Enable/disable scrollbars as appropriate
1004 calculateScrollBars();
1005}
1006
george8274ea5f32006-09-11 11:40:12 +00001007void DesktopWindow::setAutoScaling(bool as) {
1008 autoScaling = as;
george822446ed02007-03-10 08:55:35 +00001009 if (isToolbarEnabled()) refreshToolbarButtons();
george8274ea5f32006-09-11 11:40:12 +00001010 if (as) fitBufferToWindow();
1011}
1012
george822446ed02007-03-10 08:55:35 +00001013void DesktopWindow::setDesktopScale(int scale_) {
1014 if (buffer->getScale() == scale_ || scale_ <= 0) return;
1015 buffer->setScale(scale_);
george8274ea5f32006-09-11 11:40:12 +00001016 if (isToolbarEnabled()) refreshToolbarButtons();
george826d6e4af2007-03-10 12:49:34 +00001017 if (!isAutoScaling() && !isFullscreen()) resizeDesktopWindowToBuffer();
george82770bbbc2007-03-12 10:48:09 +00001018 printScale();
george82858a4642007-01-27 15:32:27 +00001019 InvalidateRect(frameHandle, 0, FALSE);
george8204a77712006-05-29 14:18:14 +00001020}
1021
george823c68f5f2006-09-05 06:17:01 +00001022void DesktopWindow::fitBufferToWindow(bool repaint) {
1023 double scale_ratio;
1024 double resized_aspect_corr = double(client_size.width()) / client_size.height();
1025 DWORD style = GetWindowLong(frameHandle, GWL_STYLE);
1026 if (style & (WS_VSCROLL | WS_HSCROLL)) {
1027 style &= ~(WS_VSCROLL | WS_HSCROLL);
1028 SetWindowLong(frameHandle, GWL_STYLE, style);
1029 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1030 // Update the cached client size
1031 RECT r;
1032 GetClientRect(frameHandle, &r);
1033 client_size = Rect(r.left, r.top, r.right, r.bottom);
1034 }
1035 if (resized_aspect_corr > aspect_corr) {
george82770bbbc2007-03-12 10:48:09 +00001036 scale_ratio = (double)client_size.height() / buffer->getSrcHeight();
1037 buffer->setScaleWindowSize(ceil(buffer->getSrcWidth()*scale_ratio), client_size.height());
george823c68f5f2006-09-05 06:17:01 +00001038 } else {
george82770bbbc2007-03-12 10:48:09 +00001039 scale_ratio = (double)client_size.width() / buffer->getSrcWidth();
1040 buffer->setScaleWindowSize(client_size.width(), ceil(buffer->getSrcHeight()*scale_ratio));
george823c68f5f2006-09-05 06:17:01 +00001041 }
george82770bbbc2007-03-12 10:48:09 +00001042 printScale();
1043 InvalidateRect(frameHandle, 0, FALSE);
1044}
1045
1046void DesktopWindow::printScale() {
1047 char *newTitle = new char[strlen(desktopName)+20];
1048 sprintf(newTitle, "%s @ %i%%", desktopName, getDesktopScale());
1049 SetWindowText(handle, TStr(newTitle));
1050 delete [] newTitle;
george823c68f5f2006-09-05 06:17:01 +00001051}
1052
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001053void
1054DesktopWindow::setCursor(int w, int h, const Point& hotspot, void* data, void* mask) {
1055 hideLocalCursor();
1056
1057 cursor.hotspot = hotspot;
1058
1059 cursor.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001060 cursor.setPF(buffer->getScaledPixelFormat());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001061 cursor.imageRect(cursor.getRect(), data);
1062 memcpy(cursor.mask.buf, mask, cursor.maskLen());
1063 cursor.crop();
1064
1065 cursorBacking.setSize(w, h);
george827c721cc2006-09-23 07:09:37 +00001066 cursorBacking.setPF(buffer->getScaledPixelFormat());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001067
1068 cursorAvailable = true;
1069
1070 showLocalCursor();
1071}
1072
1073PixelFormat
1074DesktopWindow::getNativePF() const {
1075 vlog.debug("getNativePF()");
1076 return WindowDC(handle).getPF();
1077}
1078
1079
1080void
1081DesktopWindow::refreshWindowPalette(int start, int count) {
1082 vlog.debug("refreshWindowPalette(%d, %d)", start, count);
1083
1084 Colour colours[256];
1085 if (count > 256) {
1086 vlog.debug("%d palette entries", count);
1087 throw rdr::Exception("too many palette entries");
1088 }
1089
1090 // Copy the palette from the DIBSectionBuffer
1091 ColourMap* cm = buffer->getColourMap();
1092 if (!cm) return;
1093 for (int i=0; i<count; i++) {
1094 int r, g, b;
1095 cm->lookup(i, &r, &g, &b);
1096 colours[i].r = r;
1097 colours[i].g = g;
1098 colours[i].b = b;
1099 }
1100
1101 // Set the window palette
1102 windowPalette.setEntries(start, count, colours);
1103
1104 // Cause the window to be redrawn
1105 palette_changed = true;
1106 InvalidateRect(handle, 0, FALSE);
1107}
1108
1109
1110void DesktopWindow::calculateScrollBars() {
1111 // Calculate the required size of window
1112 DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE);
1113 DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL);
1114 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1115 DWORD old_style;
1116 RECT r;
1117 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1118 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1119 Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom);
1120
1121 if (!bumpScroll) {
1122 // We only enable scrollbars if bump-scrolling is not active.
1123 // Effectively, this means if full-screen is not active,
1124 // but I think it's better to make these things explicit.
1125
1126 // Work out whether scroll bars are required
1127 do {
1128 old_style = style;
1129
1130 if (!(style & WS_HSCROLL) && (reqd_size.width() > window_size.width())) {
1131 style |= WS_HSCROLL;
1132 reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL);
1133 }
1134 if (!(style & WS_VSCROLL) && (reqd_size.height() > window_size.height())) {
1135 style |= WS_VSCROLL;
1136 reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL);
1137 }
1138 } while (style != old_style);
1139 }
1140
1141 // Tell Windows to update the window style & cached settings
1142 if (style != current_style) {
1143 SetWindowLong(frameHandle, GWL_STYLE, style);
1144 SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
1145 }
1146
1147 // Update the scroll settings
1148 SCROLLINFO si;
1149 if (style & WS_VSCROLL) {
1150 si.cbSize = sizeof(si);
1151 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1152 si.nMin = 0;
1153 si.nMax = buffer->height();
1154 si.nPage = buffer->height() - (reqd_size.height() - window_size.height());
1155 maxscrolloffset.y = max(0, si.nMax-si.nPage);
1156 scrolloffset.y = min(maxscrolloffset.y, scrolloffset.y);
1157 si.nPos = scrolloffset.y;
1158 SetScrollInfo(frameHandle, SB_VERT, &si, TRUE);
1159 }
1160 if (style & WS_HSCROLL) {
1161 si.cbSize = sizeof(si);
1162 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1163 si.nMin = 0;
1164 si.nMax = buffer->width();
1165 si.nPage = buffer->width() - (reqd_size.width() - window_size.width());
1166 maxscrolloffset.x = max(0, si.nMax-si.nPage);
1167 scrolloffset.x = min(maxscrolloffset.x, scrolloffset.x);
1168 si.nPos = scrolloffset.x;
1169 SetScrollInfo(frameHandle, SB_HORZ, &si, TRUE);
1170 }
1171
1172 // Update the cached client size
1173 GetClientRect(frameHandle, &r);
1174 client_size = Rect(r.left, r.top, r.right, r.bottom);
1175}
1176
george82858a4642007-01-27 15:32:27 +00001177void DesktopWindow::resizeDesktopWindowToBuffer() {
1178 RECT r;
1179 DWORD style = GetWindowLong(frameHandle, GWL_STYLE) & ~(WS_VSCROLL | WS_HSCROLL);
1180 DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE);
1181
1182 // Calculate the required size of the desktop window
1183 SetRect(&r, 0, 0, buffer->width(), buffer->height());
1184 AdjustWindowRectEx(&r, style, FALSE, style_ex);
1185 if (isToolbarEnabled())
1186 r.bottom += tb.getHeight();
1187 AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE);
1188
1189 // Set the required size, center the main window and clip to the current monitor
1190 SetWindowPos(handle, 0, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
1191 centerWindow(handle, NULL);
1192 MonitorInfo mi(getMonitor());
1193 mi.clipTo(handle);
1194
1195 // Enable/disable scrollbars as appropriate
1196 calculateScrollBars();
1197}
1198
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001199
1200void
1201DesktopWindow::setName(const char* name) {
1202 SetWindowText(handle, TStr(name));
george82e0569e42006-09-11 15:56:10 +00001203 strCopy(desktopName, name, sizeof(desktopName));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001204}
1205
1206
1207void
1208DesktopWindow::serverCutText(const char* str, int len) {
1209 CharArray t(len+1);
1210 memcpy(t.buf, str, len);
1211 t.buf[len] = 0;
1212 clipboard.setClipText(t.buf);
1213}
1214
1215
1216void DesktopWindow::fillRect(const Rect& r, Pixel pix) {
1217 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1218 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1219 buffer->fillRect(r, pix);
1220 invalidateDesktopRect(r);
1221}
1222void DesktopWindow::imageRect(const Rect& r, void* pixels) {
1223 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1224 if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor();
1225 buffer->imageRect(r, pixels);
1226 invalidateDesktopRect(r);
1227}
1228void DesktopWindow::copyRect(const Rect& r, int srcX, int srcY) {
1229 Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r;
1230 if (cursorBackingRect.overlaps(img_rect) ||
1231 cursorBackingRect.overlaps(Rect(srcX, srcY, srcX+img_rect.width(), srcY+img_rect.height())))
1232 hideLocalCursor();
1233 buffer->copyRect(r, Point(r.tl.x-srcX, r.tl.y-srcY));
1234 invalidateDesktopRect(r);
1235}
1236
1237void DesktopWindow::invertRect(const Rect& r) {
1238 int stride;
1239 rdr::U8* p = buffer->isScaling() ? buffer->getPixelsRW(buffer->calculateScaleBoundary(r), &stride)
1240 : buffer->getPixelsRW(r, &stride);
1241 for (int y = 0; y < r.height(); y++) {
1242 for (int x = 0; x < r.width(); x++) {
1243 switch (buffer->getPF().bpp) {
1244 case 8: ((rdr::U8* )p)[x+y*stride] ^= 0xff; break;
1245 case 16: ((rdr::U16*)p)[x+y*stride] ^= 0xffff; break;
1246 case 32: ((rdr::U32*)p)[x+y*stride] ^= 0xffffffff; break;
1247 }
1248 }
1249 }
1250 invalidateDesktopRect(r);
1251}