blob: 8b741c0d5714f29edba1fe11024bbe13c42c4de5 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +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// All-new VNC viewer for X.
20//
21
22#include <string.h>
23#include <stdio.h>
24#include <ctype.h>
25#include <stdlib.h>
26#include <sys/time.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/wait.h>
30#include <unistd.h>
31#include <errno.h>
32#include <signal.h>
Adam Tkac04b7fd22008-03-19 16:14:48 +000033#include <locale.h>
Adam Tkac21779fd2010-10-29 12:17:19 +000034#include <os/os.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035#include <rfb/Logger_stdio.h>
Adam Tkac27b2f772010-11-18 13:33:57 +000036#include <rfb/SecurityClient.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000037#include <rfb/LogWriter.h>
38#include <network/TcpSocket.h>
39#include "TXWindow.h"
40#include "TXMsgBox.h"
41#include "CConn.h"
42
Adam Tkac0c65d8a2008-04-17 16:37:08 +000043#include "gettext.h"
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000044#define _(String) gettext (String)
45#define gettext_noop(String) String
46#define N_(String) gettext_noop (String)
47
48rfb::LogWriter vlog("main");
49
50using namespace network;
51using namespace rfb;
52using namespace std;
53
54IntParameter pointerEventInterval("PointerEventInterval",
55 "Time in milliseconds to rate-limit"
56 " successive pointer events", 0);
57IntParameter wmDecorationWidth("WMDecorationWidth", "Width of window manager "
58 "decoration around a window", 6);
59IntParameter wmDecorationHeight("WMDecorationHeight", "Height of window "
60 "manager decoration around a window", 24);
61StringParameter passwordFile("PasswordFile",
62 "Password file for VNC authentication", "");
Adam Tkac1d15e2d2010-04-23 14:06:38 +000063AliasParameter passwd("passwd", "Alias for PasswordFile", &passwordFile);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000064
65BoolParameter useLocalCursor("UseLocalCursor",
66 "Render the mouse cursor locally", true);
67BoolParameter dotWhenNoCursor("DotWhenNoCursor",
68 "Show the dot cursor when the server sends an "
69 "invisible cursor", true);
70BoolParameter autoSelect("AutoSelect",
71 "Auto select pixel format and encoding. "
72 "Default if PreferredEncoding and FullColor are not specified.",
73 true);
74BoolParameter fullColour("FullColor",
75 "Use full color", true);
76AliasParameter fullColourAlias("FullColour", "Alias for FullColor", &fullColour);
77IntParameter lowColourLevel("LowColorLevel",
78 "Color level to use on slow connections. "
79 "0 = Very Low (8 colors), 1 = Low (64 colors), "
80 "2 = Medium (256 colors)", 2);
81AliasParameter lowColourLevelAlias("LowColourLevel", "Alias for LowColorLevel", &lowColourLevel);
82StringParameter preferredEncoding("PreferredEncoding",
83 "Preferred encoding to use (Tight, ZRLE, Hextile or"
84 " Raw)", "Tight");
85BoolParameter fullScreen("FullScreen", "Full screen mode", false);
86BoolParameter viewOnly("ViewOnly",
87 "Don't send any mouse or keyboard events to the server",
88 false);
89BoolParameter shared("Shared",
90 "Don't disconnect other viewers upon connection - "
91 "share the desktop instead",
92 false);
93BoolParameter acceptClipboard("AcceptClipboard",
94 "Accept clipboard changes from the server",
95 true);
96BoolParameter sendClipboard("SendClipboard",
97 "Send clipboard changes to the server", true);
98BoolParameter sendPrimary("SendPrimary",
99 "Send the primary selection and cut buffer to the "
100 "server as well as the clipboard selection",
101 true);
102
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000103StringParameter desktopSize("DesktopSize",
104 "Reconfigure desktop size on the server on "
105 "connect (if possible)", "");
106
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000107BoolParameter listenMode("listen", "Listen for connections from VNC servers",
108 false);
109StringParameter geometry("geometry", "X geometry specification", "");
110StringParameter displayname("display", "The X display", "");
111
112StringParameter via("via", "Gateway to tunnel via", "");
113
114BoolParameter customCompressLevel("CustomCompressLevel",
115 "Use custom compression level. "
116 "Default if CompressLevel is specified.", false);
117
118IntParameter compressLevel("CompressLevel",
119 "Use specified compression level"
120 "0 = Low, 9 = High",
121 6);
122
123BoolParameter noJpeg("NoJPEG",
124 "Disable lossy JPEG compression in Tight encoding.",
125 false);
126
127IntParameter qualityLevel("QualityLevel",
128 "JPEG quality level. "
129 "0 = Low, 9 = High",
Pierre Ossman7dfa22e2009-03-12 13:03:22 +0000130 8);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000131
132char aboutText[1024];
133char* programName;
134extern char buildtime[];
135
136static void CleanupSignalHandler(int sig)
137{
138 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
139 // exit() rather than the default which is to abort.
140 vlog.info("CleanupSignalHandler called");
141 exit(1);
142}
143
144// XLoginIconifier is a class which iconifies the XDM login window when it has
145// grabbed the keyboard, thus releasing the grab, allowing the viewer to use
146// the keyboard. It remaps the xlogin window on exit.
147class XLoginIconifier {
148public:
149 Display* dpy;
150 Window xlogin;
151 XLoginIconifier() : dpy(0), xlogin(0) {}
152 void iconify(Display* dpy_) {
153 dpy = dpy_;
154 if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), False, GrabModeSync,
155 GrabModeSync, CurrentTime) == GrabSuccess) {
156 XUngrabKeyboard(dpy, CurrentTime);
157 } else {
158 xlogin = TXWindow::windowWithName(dpy, DefaultRootWindow(dpy), "xlogin");
159 if (xlogin) {
160 XIconifyWindow(dpy, xlogin, DefaultScreen(dpy));
161 XSync(dpy, False);
162 }
163 }
164 }
165 ~XLoginIconifier() {
166 if (xlogin) {
167 fprintf(stderr,"~XLoginIconifier remapping xlogin\n");
168 XMapWindow(dpy, xlogin);
169 XFlush(dpy);
170 sleep(1);
171 }
172 }
173};
174
175static XLoginIconifier xloginIconifier;
176
177static void usage()
178{
179 fprintf(stderr,
180 "\nusage: %s [parameters] [host:displayNum] [parameters]\n"
181 " %s [parameters] -listen [port] [parameters]\n",
182 programName,programName);
183 fprintf(stderr,"\n"
184 "Parameters can be turned on with -<param> or off with -<param>=0\n"
185 "Parameters which take a value can be specified as "
186 "-<param> <value>\n"
187 "Other valid forms are <param>=<value> -<param>=<value> "
188 "--<param>=<value>\n"
189 "Parameter names are case-insensitive. The parameters are:\n\n");
190 Configuration::listParams(79, 14);
191 exit(1);
192}
193
194/* Tunnelling support. */
195static void
196interpretViaParam (char **gatewayHost, char **remoteHost,
197 int *remotePort, char **vncServerName,
198 int localPort)
199{
200 const int SERVER_PORT_OFFSET = 5900;
201 char *pos = strchr (*vncServerName, ':');
202 if (pos == NULL)
203 *remotePort = SERVER_PORT_OFFSET;
204 else {
205 int portOffset = SERVER_PORT_OFFSET;
206 size_t len;
207 *pos++ = '\0';
208 len = strlen (pos);
209 if (*pos == ':') {
210 /* Two colons is an absolute port number, not an offset. */
211 pos++;
212 len--;
213 portOffset = 0;
214 }
215 if (!len || strspn (pos, "-0123456789") != len )
216 usage ();
217 *remotePort = atoi (pos) + portOffset;
218 }
219
220 if (**vncServerName != '\0')
221 *remoteHost = *vncServerName;
222
Adam Tkacd36b6262009-09-04 10:57:20 +0000223 *gatewayHost = strDup (via.getValueStr ());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000224 *vncServerName = new char[50];
225 sprintf (*vncServerName, "localhost::%d", localPort);
226}
227
228#ifndef HAVE_SETENV
229int
230setenv(const char *envname, const char * envval, int overwrite)
231{
232 if (envname && envval) {
233 char * envp = NULL;
234 envp = (char*)malloc(strlen(envname) + strlen(envval) + 2);
235 if (envp) {
236 // The putenv API guarantees memory leaks when
237 // changing environment variables repeatedly.
238 sprintf(envp, "%s=%s", envname, envval);
239
240 // Cannot free envp
241 putenv(envp);
242 return(0);
243 }
244 }
245 return(-1);
246}
247#endif
248
249static void
250createTunnel (const char *gatewayHost, const char *remoteHost,
251 int remotePort, int localPort)
252{
253 char *cmd = getenv ("VNC_VIA_CMD");
254 char *percent;
255 char lport[10], rport[10];
256 sprintf (lport, "%d", localPort);
257 sprintf (rport, "%d", remotePort);
258 setenv ("G", gatewayHost, 1);
259 setenv ("H", remoteHost, 1);
260 setenv ("R", rport, 1);
261 setenv ("L", lport, 1);
262 if (!cmd)
263 cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20";
Peter Åstrand4eacc022009-02-27 10:12:14 +0000264 /* Compatibility with TigerVNC's method. */
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000265 while ((percent = strchr (cmd, '%')) != NULL)
266 *percent = '$';
267 system (cmd);
268}
269
270int main(int argc, char** argv)
271{
272 setlocale(LC_ALL, "");
Adam Tkac932e09b2008-04-17 15:45:58 +0000273 bindtextdomain(PACKAGE_NAME, LOCALEDIR);
274 textdomain(PACKAGE_NAME);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000275
Peter Åstrand4eacc022009-02-27 10:12:14 +0000276 const char englishAbout[] = N_("TigerVNC Viewer for X version %s - built %s\n"
Peter Åstrand3336ac42009-01-26 13:35:31 +0000277 "Copyright (C) 2002-2005 RealVNC Ltd.\n"
278 "Copyright (C) 2000-2006 TightVNC Group\n"
Peter Åstranda87caa62009-02-25 15:01:07 +0000279 "Copyright (C) 2004-2009 Peter Astrand for Cendio AB\n"
Peter Åstrand4eacc022009-02-27 10:12:14 +0000280 "See http://www.tigervnc.org for information on TigerVNC.");
Peter Åstrand3336ac42009-01-26 13:35:31 +0000281
Adam Tkac27b2f772010-11-18 13:33:57 +0000282 rfb::SecurityClient::setDefaults();
283
Peter Åstrand3336ac42009-01-26 13:35:31 +0000284 // Write about text to console, still using normal locale codeset
285 snprintf(aboutText, sizeof(aboutText),
286 gettext(englishAbout), PACKAGE_VERSION, buildtime);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000287 fprintf(stderr,"\n%s\n", aboutText);
288
Peter Åstrand3336ac42009-01-26 13:35:31 +0000289 // Set gettext codeset to what our GUI toolkit uses. Since we are
290 // passing strings from strerror/gai_strerror to the GUI, these must
291 // be in GUI codeset as well.
Adam Tkac932e09b2008-04-17 15:45:58 +0000292 bind_textdomain_codeset(PACKAGE_NAME, "iso-8859-1");
Peter Åstrandaa409f12009-01-26 13:17:27 +0000293 bind_textdomain_codeset("libc", "iso-8859-1");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000294
Peter Åstrand3336ac42009-01-26 13:35:31 +0000295 // Re-create the aboutText for the GUI, now using GUI codeset
296 snprintf(aboutText, sizeof(aboutText),
297 gettext(englishAbout), PACKAGE_VERSION, buildtime);
298
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000299 rfb::initStdIOLoggers();
300 rfb::LogWriter::setLogParams("*:stderr:30");
301
302 signal(SIGHUP, CleanupSignalHandler);
303 signal(SIGINT, CleanupSignalHandler);
304 signal(SIGTERM, CleanupSignalHandler);
305
306 programName = argv[0];
307 char* vncServerName = 0;
308 Display* dpy = 0;
309
Adam Tkacc58b3d12010-04-23 13:55:10 +0000310 Configuration::enableViewerParams();
311
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000312 for (int i = 1; i < argc; i++) {
313 if (Configuration::setParam(argv[i]))
314 continue;
315
316 if (argv[i][0] == '-') {
317 if (i+1 < argc) {
318 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
319 i++;
320 continue;
321 }
322 }
323 usage();
324 }
325
326 vncServerName = argv[i];
327 }
328
329 // Create .vnc in the user's home directory if it doesn't already exist
Adam Tkac21779fd2010-10-29 12:17:19 +0000330 char* homeDir = NULL;
Adam Tkacaf081722011-02-07 10:45:15 +0000331 if (getvnchomedir(&homeDir) == -1) {
332 vlog.error("Could not create VNC home directory: can't obtain home "
333 "directory path.");
334 } else {
335 int result = mkdir(homeDir, 0755);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000336 if (result == -1 && errno != EEXIST)
Adam Tkacaf081722011-02-07 10:45:15 +0000337 vlog.error("Could not create VNC home directory: %s.", strerror(errno));
Adam Tkac21779fd2010-10-29 12:17:19 +0000338 delete [] homeDir;
339 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000340
341 if (!::autoSelect.hasBeenSet()) {
342 // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
343 ::autoSelect.setParam(!::preferredEncoding.hasBeenSet()
344 && !::fullColour.hasBeenSet()
345 && !::fullColourAlias.hasBeenSet());
346 }
Adam Tkac21a5cc82009-10-07 15:14:33 +0000347 if (!::fullColour.hasBeenSet() && !::fullColourAlias.hasBeenSet()) {
348 // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
349 if (!::autoSelect && (::lowColourLevel.hasBeenSet() ||
350 ::lowColourLevelAlias.hasBeenSet())) {
351 ::fullColour.setParam(false);
352 }
353 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000354 if (!::customCompressLevel.hasBeenSet()) {
355 // Default to CustomCompressLevel=1 if CompressLevel is used.
356 ::customCompressLevel.setParam(::compressLevel.hasBeenSet());
357 }
358
359 try {
360 /* Tunnelling support. */
361 if (strlen (via.getValueStr ()) > 0) {
362 char *gatewayHost = "";
363 char *remoteHost = "localhost";
364 int localPort = findFreeTcpPort ();
365 int remotePort;
366 if (!vncServerName)
367 usage();
368 interpretViaParam (&gatewayHost, &remoteHost, &remotePort,
369 &vncServerName, localPort);
370 createTunnel (gatewayHost, remoteHost, remotePort, localPort);
371 }
372
373 Socket* sock = 0;
374
375 if (listenMode) {
376 int port = 5500;
377 if (vncServerName && isdigit(vncServerName[0]))
378 port = atoi(vncServerName);
379
Adam Tkac93ff5db2010-02-05 15:54:10 +0000380 TcpListener listener(NULL, port);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000381
382 vlog.info("Listening on port %d\n",port);
383
384 while (true) {
385 sock = listener.accept();
386 int pid = fork();
387 if (pid < 0) { perror("fork"); exit(1); }
388 if (pid == 0) break; // child
389 delete sock;
390 int status;
391 while (wait3(&status, WNOHANG, 0) > 0) ;
392 }
393 }
394
395 CharArray displaynameStr(displayname.getData());
396 if (!(dpy = XOpenDisplay(TXWindow::strEmptyToNull(displaynameStr.buf)))) {
397 fprintf(stderr,"%s: unable to open display \"%s\"\n",
398 programName, XDisplayName(displaynameStr.buf));
399 exit(1);
400 }
401
402 TXWindow::init(dpy, "Vncviewer");
403 xloginIconifier.iconify(dpy);
404 CConn cc(dpy, argc, argv, sock, vncServerName, listenMode);
405
406 // X events are processed whenever reading from the socket would block.
407
408 while (true) {
409 cc.getInStream()->check(1);
410 cc.processMsg();
411 }
412
413 } catch (rdr::EndOfStream& e) {
414 vlog.info(e.str());
415 } catch (rdr::Exception& e) {
416 vlog.error(e.str());
417 if (dpy) {
418 TXMsgBox msgBox(dpy, e.str(), MB_OK, "VNC Viewer: Information");
419 msgBox.show();
420 }
421 return 1;
422 }
423
424 return 0;
425}