blob: 6a6561d78e5addbf081f3d97144edb955bdbedb8 [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
Adam Tkac243fd7c2010-12-08 13:47:41 +000019#ifdef HAVE_CONFIG_H
20#include <config.h>
21#endif
22
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000023#include <winsock2.h>
24#include <vncviewer/UserPasswdDialog.h>
25#include <vncviewer/CConn.h>
26#include <vncviewer/CConnThread.h>
27#include <vncviewer/resource.h>
28#include <rfb/encodings.h>
Adam Tkac5a0caed2010-04-23 13:58:10 +000029#include <rfb/Security.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000030#include <rfb/CMsgWriter.h>
31#include <rfb/Configuration.h>
Adam Tkac243fd7c2010-12-08 13:47:41 +000032#ifdef HAVE_GNUTLS
33#include <rfb/CSecurityTLS.h>
34#endif
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000035#include <rfb/LogWriter.h>
36#include <rfb_win32/AboutDialog.h>
37
38using namespace rfb;
39using namespace rfb::win32;
40using namespace rdr;
41
42// - Statics & consts
43
44static LogWriter vlog("CConn");
45
46
47const int IDM_FULLSCREEN = ID_FULLSCREEN;
48const int IDM_SEND_MENU_KEY = ID_SEND_MENU_KEY;
49const int IDM_SEND_CAD = ID_SEND_CAD;
50const int IDM_SEND_CTLESC = ID_SEND_CTLESC;
51const int IDM_ABOUT = ID_ABOUT;
52const int IDM_OPTIONS = ID_OPTIONS;
53const int IDM_INFO = ID_INFO;
54const int IDM_NEWCONN = ID_NEW_CONNECTION;
55const int IDM_REQUEST_REFRESH = ID_REQUEST_REFRESH;
56const int IDM_CTRL_KEY = ID_CTRL_KEY;
57const int IDM_ALT_KEY = ID_ALT_KEY;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000058const int IDM_CONN_SAVE_AS = ID_CONN_SAVE_AS;
george824f72ab32006-09-10 05:13:45 +000059const int IDM_ZOOM_IN = ID_ZOOM_IN;
60const int IDM_ZOOM_OUT = ID_ZOOM_OUT;
61const int IDM_ACTUAL_SIZE = ID_ACTUAL_SIZE;
62const int IDM_AUTO_SIZE = ID_AUTO_SIZE;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000063
64
65static IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
66 "pixel data - a debugging feature", 0);
67
george82b6d87aa2006-09-11 07:00:59 +000068const int scaleValues[9] = {10, 25, 50, 75, 90, 100, 125, 150, 200};
69const int scaleCount = 9;
70
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000071
72//
73// -=- CConn implementation
74//
75
76RegKey CConn::userConfigKey;
77
78
79CConn::CConn()
Peter Åstrand2dd9eea2008-12-11 08:23:55 +000080 : window(0), sameMachine(false), encodingChange(false), formatChange(false),
81 lastUsedEncoding_(encodingRaw), sock(0), sockEvent(CreateEvent(0, TRUE, FALSE, 0)),
Pierre Ossmand6d19942009-03-24 12:29:50 +000082 reverseConnection(false), requestUpdate(false), firstUpdate(true),
83 isClosed_(false) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000084}
85
86CConn::~CConn() {
87 delete window;
88}
89
90bool CConn::initialise(network::Socket* s, bool reverse) {
91 // Set the server's name for MRU purposes
92 CharArray endpoint(s->getPeerEndpoint());
93 setServerName(endpoint.buf);
94 if (!options.host.buf)
95 options.setHost(endpoint.buf);
96
97 // Initialise the underlying CConnection
98 setStreams(&s->inStream(), &s->outStream());
99
100 // Enable processing of window messages while blocked on I/O
101 s->inStream().setBlockCallback(this);
102
103 // Initialise the viewer options
104 applyOptions(options);
105
Adam Tkacb10489b2010-04-23 14:16:04 +0000106 CSecurity::upg = this;
Adam Tkac243fd7c2010-12-08 13:47:41 +0000107#ifdef HAVE_GNUTLS
108 CSecurityTLS::msg = this;
109#endif
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000110
111 // Start the RFB protocol
112 sock = s;
113 reverseConnection = reverse;
114 initialiseProtocol();
115
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000116 return true;
117}
118
119
120void
121CConn::applyOptions(CConnOptions& opt) {
122 // - If any encoding-related settings have changed then we must
123 // notify the server of the new settings
124 encodingChange |= ((options.useLocalCursor != opt.useLocalCursor) ||
125 (options.useDesktopResize != opt.useDesktopResize) ||
126 (options.customCompressLevel != opt.customCompressLevel) ||
127 (options.compressLevel != opt.compressLevel) ||
128 (options.noJpeg != opt.noJpeg) ||
129 (options.qualityLevel != opt.qualityLevel) ||
130 (options.preferredEncoding != opt.preferredEncoding));
131
132 // - If the preferred pixel format has changed then notify the server
133 formatChange |= (options.fullColour != opt.fullColour);
134 if (!opt.fullColour)
135 formatChange |= (options.lowColourLevel != opt.lowColourLevel);
136
137 // - Save the new set of options
138 options = opt;
139
140 // - Set optional features in ConnParams
141 cp.supportsLocalCursor = options.useLocalCursor;
142 cp.supportsDesktopResize = options.useDesktopResize;
Pierre Ossmand6d19942009-03-24 12:29:50 +0000143 cp.supportsExtendedDesktopSize = options.useDesktopResize;
Peter Åstrandc39e0782009-01-15 12:21:42 +0000144 cp.supportsDesktopRename = true;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000145 cp.customCompressLevel = options.customCompressLevel;
146 cp.compressLevel = options.compressLevel;
147 cp.noJpeg = options.noJpeg;
148 cp.qualityLevel = options.qualityLevel;
149
150 // - Configure connection sharing on/off
151 setShared(options.shared);
152
153 // - Whether to use protocol 3.3 for legacy compatibility
154 setProtocol3_3(options.protocol3_3);
155
156 // - Apply settings that affect the window, if it is visible
157 if (window) {
158 window->setMonitor(options.monitor.buf);
159 window->setFullscreen(options.fullScreen);
160 window->setEmulate3(options.emulate3);
161 window->setPointerEventInterval(options.pointerEventInterval);
162 window->setMenuKey(options.menuKey);
163 window->setDisableWinKeys(options.disableWinKeys);
164 window->setShowToolbar(options.showToolbar);
george82770bbbc2007-03-12 10:48:09 +0000165 window->printScale();
george82ffc14a62006-09-05 06:51:41 +0000166 if (options.autoScaling) {
167 window->setAutoScaling(true);
168 } else {
169 window->setAutoScaling(false);
170 window->setDesktopScale(options.scale);
171 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000172 if (!options.useLocalCursor)
173 window->setCursor(0, 0, Point(), 0, 0);
174 }
175}
176
177
178void
179CConn::displayChanged() {
180 // Display format has changed - recalculate the full-colour pixel format
181 calculateFullColourPF();
182}
183
184void
185CConn::paintCompleted() {
186 // A repaint message has just completed - request next update if necessary
187 requestNewUpdate();
188}
189
190bool
191CConn::sysCommand(WPARAM wParam, LPARAM lParam) {
192 // - If it's one of our (F8 Menu) messages
193 switch (wParam) {
194 case IDM_FULLSCREEN:
195 options.fullScreen = !window->isFullscreen();
196 window->setFullscreen(options.fullScreen);
197 return true;
george824f72ab32006-09-10 05:13:45 +0000198 case IDM_ZOOM_IN:
george824f72ab32006-09-10 05:13:45 +0000199 case IDM_ZOOM_OUT:
george82b6d87aa2006-09-11 07:00:59 +0000200 {
201 if (options.autoScaling) {
202 options.scale = window->getDesktopScale();
203 options.autoScaling = false;
204 window->setAutoScaling(false);
205 }
Peter Åstrand145d1f82010-02-10 07:26:56 +0000206 if (wParam == (unsigned)IDM_ZOOM_IN) {
george82b6d87aa2006-09-11 07:00:59 +0000207 for (int i = 0; i < scaleCount; i++)
208 if (options.scale < scaleValues[i]) {
209 options.scale = scaleValues[i];
210 break;
211 }
212 } else {
213 for (int i = scaleCount-1; i >= 0; i--)
214 if (options.scale > scaleValues[i]) {
215 options.scale = scaleValues[i];
216 break;
217 }
218 }
219 if (options.scale != window->getDesktopScale())
220 window->setDesktopScale(options.scale);
221 }
george824f72ab32006-09-10 05:13:45 +0000222 return true;
223 case IDM_ACTUAL_SIZE:
224 if (options.autoScaling) {
225 options.autoScaling = false;
226 window->setAutoScaling(false);
227 }
george824f72ab32006-09-10 05:13:45 +0000228 options.scale = 100;
229 window->setDesktopScale(100);
230 return true;
231 case IDM_AUTO_SIZE:
george8274ea5f32006-09-11 11:40:12 +0000232 options.autoScaling = !options.autoScaling;
233 window->setAutoScaling(options.autoScaling);
234 if (!options.autoScaling) options.scale = window->getDesktopScale();
george824f72ab32006-09-10 05:13:45 +0000235 return true;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000236 case IDM_SHOW_TOOLBAR:
237 options.showToolbar = !window->isToolbarEnabled();
238 window->setShowToolbar(options.showToolbar);
239 return true;
240 case IDM_CTRL_KEY:
241 window->kbd.keyEvent(this, VK_CONTROL, 0, !window->kbd.keyPressed(VK_CONTROL));
242 return true;
243 case IDM_ALT_KEY:
244 window->kbd.keyEvent(this, VK_MENU, 0, !window->kbd.keyPressed(VK_MENU));
245 return true;
246 case IDM_SEND_MENU_KEY:
247 window->kbd.keyEvent(this, options.menuKey, 0, true);
248 window->kbd.keyEvent(this, options.menuKey, 0, false);
249 return true;
250 case IDM_SEND_CAD:
251 window->kbd.keyEvent(this, VK_CONTROL, 0, true);
252 window->kbd.keyEvent(this, VK_MENU, 0, true);
253 window->kbd.keyEvent(this, VK_DELETE, 0x1000000, true);
254 window->kbd.keyEvent(this, VK_DELETE, 0x1000000, false);
255 window->kbd.keyEvent(this, VK_MENU, 0, false);
256 window->kbd.keyEvent(this, VK_CONTROL, 0, false);
257 return true;
258 case IDM_SEND_CTLESC:
259 window->kbd.keyEvent(this, VK_CONTROL, 0, true);
260 window->kbd.keyEvent(this, VK_ESCAPE, 0, true);
261 window->kbd.keyEvent(this, VK_ESCAPE, 0, false);
262 window->kbd.keyEvent(this, VK_CONTROL, 0, false);
263 return true;
264 case IDM_REQUEST_REFRESH:
265 try {
266 writer()->writeFramebufferUpdateRequest(Rect(0,0,cp.width,cp.height), false);
267 requestUpdate = false;
268 } catch (rdr::Exception& e) {
269 close(e.str());
270 }
271 return true;
272 case IDM_NEWCONN:
273 {
Peter Åstrand145d1f82010-02-10 07:26:56 +0000274 new CConnThread;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000275 }
276 return true;
277 case IDM_OPTIONS:
278 // Update the monitor device name in the CConnOptions instance
279 options.monitor.replaceBuf(window->getMonitor());
280 showOptionsDialog();
281 return true;
282 case IDM_INFO:
283 infoDialog.showDialog(this);
284 return true;
285 case IDM_ABOUT:
286 AboutDialog::instance.showDialog();
287 return true;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000288 case IDM_CONN_SAVE_AS:
289 return true;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000290 };
291 return false;
292}
293
294
295void
296CConn::closeWindow() {
297 vlog.info("window closed");
298 close();
299}
300
301
302void
303CConn::refreshMenu(bool enableSysItems) {
304 HMENU menu = GetSystemMenu(window->getHandle(), FALSE);
305
306 if (!enableSysItems) {
307 // Gray out menu items that might cause a World Of Pain
308 EnableMenuItem(menu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED);
309 EnableMenuItem(menu, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
310 EnableMenuItem(menu, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
311 EnableMenuItem(menu, SC_MINIMIZE, MF_BYCOMMAND | MF_ENABLED);
312 EnableMenuItem(menu, SC_MAXIMIZE, MF_BYCOMMAND | MF_ENABLED);
313 }
314
315 // Update the modifier key menu items
316 UINT ctrlCheckFlags = window->kbd.keyPressed(VK_CONTROL) ? MF_CHECKED : MF_UNCHECKED;
317 UINT altCheckFlags = window->kbd.keyPressed(VK_MENU) ? MF_CHECKED : MF_UNCHECKED;
318 CheckMenuItem(menu, IDM_CTRL_KEY, MF_BYCOMMAND | ctrlCheckFlags);
319 CheckMenuItem(menu, IDM_ALT_KEY, MF_BYCOMMAND | altCheckFlags);
320
321 // Ensure that the Send <MenuKey> menu item has the correct text
322 if (options.menuKey) {
323 TCharArray menuKeyStr(options.menuKeyName());
324 TCharArray tmp(_tcslen(menuKeyStr.buf) + 6);
325 _stprintf(tmp.buf, _T("Send %s"), menuKeyStr.buf);
326 if (!ModifyMenu(menu, IDM_SEND_MENU_KEY, MF_BYCOMMAND | MF_STRING, IDM_SEND_MENU_KEY, tmp.buf))
327 InsertMenu(menu, IDM_SEND_CAD, MF_BYCOMMAND | MF_STRING, IDM_SEND_MENU_KEY, tmp.buf);
328 } else {
329 RemoveMenu(menu, IDM_SEND_MENU_KEY, MF_BYCOMMAND);
330 }
331
332 // Set the menu fullscreen option tick
333 CheckMenuItem(menu, IDM_FULLSCREEN, (window->isFullscreen() ? MF_CHECKED : 0) | MF_BYCOMMAND);
334
335 // Set the menu toolbar option tick
336 int toolbarFlags = window->isToolbarEnabled() ? MF_CHECKED : 0;
337 CheckMenuItem(menu, IDM_SHOW_TOOLBAR, MF_BYCOMMAND | toolbarFlags);
338
339 // In the full-screen mode, "Show toolbar" should be grayed.
340 toolbarFlags = window->isFullscreen() ? MF_GRAYED : MF_ENABLED;
341 EnableMenuItem(menu, IDM_SHOW_TOOLBAR, MF_BYCOMMAND | toolbarFlags);
342}
343
344
345void
346CConn::blockCallback() {
347 // - An InStream has blocked on I/O while processing an RFB message
348 // We re-enable socket event notifications, so we'll know when more
349 // data is available, then we sit and dispatch window events until
350 // the notification arrives.
351 if (!isClosed()) {
352 if (WSAEventSelect(sock->getFd(), sockEvent, FD_READ | FD_CLOSE) == SOCKET_ERROR)
353 throw rdr::SystemException("Unable to wait for sokcet data", WSAGetLastError());
354 }
355 while (true) {
356 // If we have closed then we can't block waiting for data
357 if (isClosed())
358 throw rdr::EndOfStream();
359
360 // Wait for socket data, or a message to process
361 DWORD result = MsgWaitForMultipleObjects(1, &sockEvent.h, FALSE, INFINITE, QS_ALLINPUT);
362 if (result == WAIT_OBJECT_0) {
363 // - Network event notification. Return control to I/O routine.
364 break;
365 } else if (result == WAIT_FAILED) {
366 // - The wait operation failed - raise an exception
367 throw rdr::SystemException("blockCallback wait error", GetLastError());
368 }
369
370 // - There should be a message in the message queue
371 MSG msg;
372 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
373 // IMPORTANT: We mustn't call TranslateMessage() here, because instead we
374 // call ToAscii() in CKeyboard::keyEvent(). ToAscii() stores dead key
375 // state from one call to the next, which would be messed up by calls to
376 // TranslateMessage() (actually it looks like TranslateMessage() calls
377 // ToAscii() internally).
378 DispatchMessage(&msg);
379 }
380 }
381
382 // Before we return control to the InStream, reset the network event
383 WSAEventSelect(sock->getFd(), sockEvent, 0);
384 ResetEvent(sockEvent);
385}
386
387
388void CConn::keyEvent(rdr::U32 key, bool down) {
389 if (!options.sendKeyEvents) return;
390 try {
391 writer()->keyEvent(key, down);
392 } catch (rdr::Exception& e) {
393 close(e.str());
394 }
395}
396void CConn::pointerEvent(const Point& pos, int buttonMask) {
397 if (!options.sendPtrEvents) return;
398 try {
399 writer()->pointerEvent(pos, buttonMask);
400 } catch (rdr::Exception& e) {
401 close(e.str());
402 }
403}
404void CConn::clientCutText(const char* str, int len) {
405 if (!options.clientCutText) return;
406 if (state() != RFBSTATE_NORMAL) return;
407 try {
408 writer()->clientCutText(str, len);
409 } catch (rdr::Exception& e) {
410 close(e.str());
411 }
412}
413
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000414void
415CConn::setColourMapEntries(int first, int count, U16* rgbs) {
416 vlog.debug("setColourMapEntries: first=%d, count=%d", first, count);
417 int i;
418 for (i=0;i<count;i++)
419 window->setColour(i+first, rgbs[i*3], rgbs[i*3+1], rgbs[i*3+2]);
420 // *** change to 0, 256?
421 window->refreshWindowPalette(first, count);
422}
423
424void
425CConn::bell() {
426 if (options.acceptBell)
Peter Åstrand7eb594a2008-12-11 08:27:52 +0000427 MessageBeep((UINT)-1);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000428}
429
430
431void
432CConn::setDesktopSize(int w, int h) {
433 vlog.debug("setDesktopSize %dx%d", w, h);
434
435 // Resize the window's buffer
436 if (window)
437 window->setSize(w, h);
438
439 // Tell the underlying CConnection
440 CConnection::setDesktopSize(w, h);
441}
442
Pierre Ossmand6d19942009-03-24 12:29:50 +0000443
444void
445CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
446 const rfb::ScreenSet& layout) {
Peter Åstrand145d1f82010-02-10 07:26:56 +0000447 if ((reason == (signed)reasonClient) && (result != (signed)resultSuccess)) {
Pierre Ossmand6d19942009-03-24 12:29:50 +0000448 vlog.error("SetDesktopSize failed: %d", result);
449 return;
450 }
451
452 // Resize the window's buffer
453 if (window)
454 window->setSize(w, h);
455
456 // Tell the underlying CConnection
457 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
458}
459
460
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000461void
462CConn::setCursor(int w, int h, const Point& hotspot, void* data, void* mask) {
463 if (!options.useLocalCursor) return;
464
465 // Set the window to use the new cursor
466 window->setCursor(w, h, hotspot, data, mask);
467}
468
469
470void
471CConn::close(const char* reason) {
472 // If already closed then ignore this
473 if (isClosed())
474 return;
475
476 // Hide the window, if it exists
477 if (window)
478 ShowWindow(window->getHandle(), SW_HIDE);
479
480 // Save the reason & flag that we're closed & shutdown the socket
481 isClosed_ = true;
482 closeReason_.replaceBuf(strDup(reason));
483 sock->shutdown();
484}
485
Adam Tkac243fd7c2010-12-08 13:47:41 +0000486bool CConn::showMsgBox(int flags, const char* title, const char* text)
487{
488 UINT winflags = 0;
489 int ret;
490
491 /* Translate flags */
492 if ((flags & M_OK) != 0)
493 winflags |= MB_OK;
494 if ((flags & M_OKCANCEL) != 0)
495 winflags |= MB_OKCANCEL;
496 if ((flags & M_YESNO) != 0)
497 winflags |= MB_YESNO;
498 if ((flags & M_ICONERROR) != 0)
499 winflags |= MB_ICONERROR;
500 if ((flags & M_ICONQUESTION) != 0)
501 winflags |= MB_ICONQUESTION;
502 if ((flags & M_ICONWARNING) != 0)
503 winflags |= MB_ICONWARNING;
504 if ((flags & M_ICONINFORMATION) != 0)
505 winflags |= MB_ICONINFORMATION;
506 if ((flags & M_DEFBUTTON1) != 0)
507 winflags |= MB_DEFBUTTON1;
508 if ((flags & M_DEFBUTTON2) != 0)
509 winflags |= MB_DEFBUTTON2;
510
511 ret = MessageBox(NULL, text, title, flags);
512 return (ret == IDOK || ret == IDYES) ? true : false;
513}
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000514
515void
516CConn::showOptionsDialog() {
517 optionsDialog.showDialog(this);
518}
519
520
521void
522CConn::framebufferUpdateEnd() {
523 if (debugDelay != 0) {
524 vlog.debug("debug delay %d",(int)debugDelay);
525 UpdateWindow(window->getHandle());
526 Sleep(debugDelay);
527 std::list<rfb::Rect>::iterator i;
528 for (i = debugRects.begin(); i != debugRects.end(); i++) {
529 window->invertRect(*i);
530 }
531 debugRects.clear();
532 }
Pierre Ossmanb2ff1602009-03-25 12:13:28 +0000533 window->framebufferUpdateEnd();
Pierre Ossmand6d19942009-03-24 12:29:50 +0000534
535 if (firstUpdate) {
536 int width, height;
537
538 if (cp.supportsSetDesktopSize &&
539 sscanf(options.desktopSize.buf, "%dx%d", &width, &height) == 2) {
540 ScreenSet layout;
541
542 layout = cp.screenLayout;
543
544 if (layout.num_screens() == 0)
545 layout.add_screen(rfb::Screen());
546 else if (layout.num_screens() != 1) {
547 ScreenSet::iterator iter;
548
549 while (true) {
550 iter = layout.begin();
551 ++iter;
552
553 if (iter == layout.end())
554 break;
555
556 layout.remove_screen(iter->id);
557 }
558 }
559
560 layout.begin()->dimensions.tl.x = 0;
561 layout.begin()->dimensions.tl.y = 0;
562 layout.begin()->dimensions.br.x = width;
563 layout.begin()->dimensions.br.y = height;
564
565 writer()->writeSetDesktopSize(width, height, layout);
566 }
567
568 firstUpdate = false;
569 }
570
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000571 if (options.autoSelect)
572 autoSelectFormatAndEncoding();
573
574 // Always request the next update
575 requestUpdate = true;
576
577 // Check that at least part of the window has changed
578 if (!GetUpdateRect(window->getHandle(), 0, FALSE)) {
579 if (!(GetWindowLong(window->getHandle(), GWL_STYLE) & WS_MINIMIZE))
580 requestNewUpdate();
581 }
582
583 // Make sure the local cursor is shown
584 window->showCursor();
585}
586
587
Pierre Ossman78b23592009-03-12 12:25:11 +0000588// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000589
590// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
591// to the connection speed:
592//
Pierre Ossman78b23592009-03-12 12:25:11 +0000593// First we wait for at least one second of bandwidth measurement.
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000594//
Pierre Ossman78b23592009-03-12 12:25:11 +0000595// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
596// which should be perceptually lossless.
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000597//
Pierre Ossman78b23592009-03-12 12:25:11 +0000598// If the bandwidth is below that, we choose a more lossy JPEG quality.
599//
600// If the bandwidth drops below 256 Kbps, we switch to palette mode.
601//
602// Note: The system here is fairly arbitrary and should be replaced
603// with something more intelligent at the server end.
604//
605void CConn::autoSelectFormatAndEncoding()
606{
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000607 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000608 unsigned int timeWaited = sock->inStream().timeWaited();
Pierre Ossman78b23592009-03-12 12:25:11 +0000609 bool newFullColour = options.fullColour;
610 int newQualityLevel = options.qualityLevel;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000611
Pierre Ossman78b23592009-03-12 12:25:11 +0000612 // Always use Tight
613 options.preferredEncoding = encodingTight;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000614
Pierre Ossman78b23592009-03-12 12:25:11 +0000615 // Check that we have a decent bandwidth measurement
616 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000617 return;
Pierre Ossman78b23592009-03-12 12:25:11 +0000618
619 // Select appropriate quality level
620 if (!options.noJpeg) {
621 if (kbitsPerSecond > 16000)
622 newQualityLevel = 8;
623 else
624 newQualityLevel = 6;
625
626 if (newQualityLevel != options.qualityLevel) {
627 vlog.info("Throughput %d kbit/s - changing to quality %d ",
628 kbitsPerSecond, newQualityLevel);
629 cp.qualityLevel = newQualityLevel;
630 options.qualityLevel = newQualityLevel;
631 encodingChange = true;
632 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000633 }
634
635 if (cp.beforeVersion(3, 8)) {
636 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
637 // cursors "asynchronously". If this happens in the middle of a
638 // pixel format change, the server will encode the cursor with
639 // the old format, but the client will try to decode it
640 // according to the new format. This will lead to a
641 // crash. Therefore, we do not allow automatic format change for
642 // old servers.
643 return;
644 }
645
646 // Select best color level
647 newFullColour = (kbitsPerSecond > 256);
648 if (newFullColour != options.fullColour) {
649 vlog.info("Throughput %d kbit/s - full color is now %s",
Pierre Ossman78b23592009-03-12 12:25:11 +0000650 kbitsPerSecond,
651 newFullColour ? "enabled" : "disabled");
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000652 options.fullColour = newFullColour;
653 formatChange = true;
Pierre Ossman78b23592009-03-12 12:25:11 +0000654 }
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000655}
656
657void
658CConn::requestNewUpdate() {
659 if (!requestUpdate) return;
660
661 if (formatChange) {
662 // Select the required pixel format
663 if (options.fullColour) {
664 window->setPF(fullColourPF);
665 } else {
666 switch (options.lowColourLevel) {
667 case 0:
668 window->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
669 break;
670 case 1:
671 window->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
672 break;
673 case 2:
674 window->setPF(PixelFormat(8,8,0,0,0,0,0,0,0,0));
675 break;
676 }
677 }
678
679 // Print the current pixel format
680 char str[256];
681 window->getPF().print(str, 256);
682 vlog.info("Using pixel format %s",str);
683
684 // Save the connection pixel format and tell server to use it
685 cp.setPF(window->getPF());
686 writer()->writeSetPixelFormat(cp.pf());
687
688 // Correct the local window's palette
689 if (!window->getNativePF().trueColour)
690 window->refreshWindowPalette(0, 1 << cp.pf().depth);
691 }
692
693 if (encodingChange) {
694 vlog.info("Using %s encoding",encodingName(options.preferredEncoding));
695 writer()->writeSetEncodings(options.preferredEncoding, true);
696 }
697
698 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
699 !formatChange);
700
701 encodingChange = formatChange = requestUpdate = false;
702}
703
704
705void
706CConn::calculateFullColourPF() {
707 // If the server is palette based then use palette locally
708 // Also, don't bother doing bgr222
709 if (!serverDefaultPF.trueColour || (serverDefaultPF.depth < 6)) {
710 fullColourPF = serverDefaultPF;
711 options.fullColour = true;
712 } else {
713 // If server is trueColour, use lowest depth PF
714 PixelFormat native = window->getNativePF();
715 if ((serverDefaultPF.bpp < native.bpp) ||
716 ((serverDefaultPF.bpp == native.bpp) &&
717 (serverDefaultPF.depth < native.depth)))
718 fullColourPF = serverDefaultPF;
719 else
720 fullColourPF = window->getNativePF();
721 }
722 formatChange = true;
723}
724
725
726void
727CConn::setName(const char* name) {
728 if (window)
729 window->setName(name);
730 CConnection::setName(name);
731}
732
733
734void CConn::serverInit() {
735 CConnection::serverInit();
736
737 // If using AutoSelect with old servers, start in FullColor
738 // mode. See comment in autoSelectFormatAndEncoding.
739 if (cp.beforeVersion(3, 8) && options.autoSelect) {
740 options.fullColour = true;
741 }
742
743 // Show the window
744 window = new DesktopWindow(this);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000745
746 // Update the window menu
747 HMENU wndmenu = GetSystemMenu(window->getHandle(), FALSE);
748 int toolbarChecked = options.showToolbar ? MF_CHECKED : 0;
749
750 AppendMenu(wndmenu, MF_SEPARATOR, 0, 0);
751 AppendMenu(wndmenu, MF_STRING, IDM_FULLSCREEN, _T("&Full screen"));
752 AppendMenu(wndmenu, MF_STRING | toolbarChecked, IDM_SHOW_TOOLBAR,
753 _T("Show tool&bar"));
754 AppendMenu(wndmenu, MF_SEPARATOR, 0, 0);
755 AppendMenu(wndmenu, MF_STRING, IDM_CTRL_KEY, _T("Ctr&l"));
756 AppendMenu(wndmenu, MF_STRING, IDM_ALT_KEY, _T("Al&t"));
757 AppendMenu(wndmenu, MF_STRING, IDM_SEND_CAD, _T("Send Ctrl-Alt-&Del"));
758 AppendMenu(wndmenu, MF_STRING, IDM_SEND_CTLESC, _T("Send Ctrl-&Esc"));
759 AppendMenu(wndmenu, MF_STRING, IDM_REQUEST_REFRESH, _T("Refres&h Screen"));
760 AppendMenu(wndmenu, MF_SEPARATOR, 0, 0);
761 AppendMenu(wndmenu, MF_STRING, IDM_NEWCONN, _T("Ne&w Connection..."));
762 AppendMenu(wndmenu, MF_STRING, IDM_OPTIONS, _T("&Options..."));
763 AppendMenu(wndmenu, MF_STRING, IDM_INFO, _T("Connection &Info..."));
764 AppendMenu(wndmenu, MF_STRING, IDM_ABOUT, _T("&About..."));
Pierre Ossman0df7c7a2009-04-09 12:00:08 +0000765
766 // Set window attributes
767 window->setName(cp.name());
768 window->setShowToolbar(options.showToolbar);
769 window->setSize(cp.width, cp.height);
770 applyOptions(options);
771
772 // Save the server's current format
773 serverDefaultPF = cp.pf();
774
775 // Calculate the full-colour format to use
776 calculateFullColourPF();
777
778 // Request the initial update
779 vlog.info("requesting initial update");
780 formatChange = encodingChange = requestUpdate = true;
781 requestNewUpdate();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000782}
783
784void
Adam Tkacacf6c6b2009-02-13 12:42:05 +0000785CConn::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000786 if (!options.serverCutText) return;
787 window->serverCutText(str, len);
788}
789
790
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000791void CConn::beginRect(const Rect& r, int encoding) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000792 sock->inStream().startTiming();
793}
794
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000795void CConn::endRect(const Rect& r, int encoding) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000796 sock->inStream().stopTiming();
797 lastUsedEncoding_ = encoding;
798 if (debugDelay != 0) {
799 window->invertRect(r);
800 debugRects.push_back(r);
801 }
802}
803
804void CConn::fillRect(const Rect& r, Pixel pix) {
805 window->fillRect(r, pix);
806}
807void CConn::imageRect(const Rect& r, void* pixels) {
808 window->imageRect(r, pixels);
809}
810void CConn::copyRect(const Rect& r, int srcX, int srcY) {
811 window->copyRect(r, srcX, srcY);
812}
813
814void CConn::getUserPasswd(char** user, char** password) {
815 if (!user && options.passwordFile.buf[0]) {
816 FILE* fp = fopen(options.passwordFile.buf, "rb");
817 if (fp) {
818 char data[256];
819 int datalen = fread(data, 1, 256, fp);
820 fclose(fp);
821 if (datalen == 8) {
822 ObfuscatedPasswd obfPwd;
823 obfPwd.buf = data;
824 obfPwd.length = datalen;
825 PlainPasswd passwd(obfPwd);
Peter Åstrand8ace9152008-12-03 11:57:18 +0000826 obfPwd.takeBuf();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000827 *password = strDup(passwd.buf);
Peter Åstrand8ace9152008-12-03 11:57:18 +0000828 memset(data, 0, sizeof(data));
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000829 }
830 }
831 }
832 if (user && options.userName.buf)
833 *user = strDup(options.userName.buf);
834 if (password && options.password.buf)
835 *password = strDup(options.password.buf);
836 if ((user && !*user) || (password && !*password)) {
837 // Missing username or password - prompt the user
838 UserPasswdDialog userPasswdDialog;
Adam Tkacf324dc42010-04-23 14:10:17 +0000839 userPasswdDialog.setCSecurity(csecurity);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000840 userPasswdDialog.getUserPasswd(user, password);
841 }
842 if (user) options.setUserName(*user);
843 if (password) options.setPassword(*password);
844}
845