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