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