blob: 37d63a6c53e5e5636d281b05b40d309feabe6bdc [file] [log] [blame]
Pierre Ossman5156d5e2011-03-09 09:42:34 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
DRCb65bb932011-06-24 03:17:00 +00003 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossman5156d5e2011-03-09 09:42:34 +00004 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20
Peter Åstrandc359f362011-08-23 12:04:46 +000021#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
Pierre Ossman5156d5e2011-03-09 09:42:34 +000025#include <string.h>
26#include <stdio.h>
Pierre Ossman2a7a8d62013-02-15 08:33:39 +000027#include <ctype.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000028#include <stdlib.h>
29#include <errno.h>
30#include <signal.h>
31#include <locale.h>
32#include <sys/stat.h>
33
34#ifdef WIN32
35#include <direct.h>
36#define mkdir(path, mode) _mkdir(path)
37#endif
38
Pierre Ossman5156d5e2011-03-09 09:42:34 +000039#include <rfb/Logger_stdio.h>
40#include <rfb/SecurityClient.h>
41#include <rfb/Security.h>
42#ifdef HAVE_GNUTLS
43#include <rfb/CSecurityTLS.h>
44#endif
45#include <rfb/LogWriter.h>
46#include <rfb/Timer.h>
Peter Åstrand8a2b0812012-08-08 11:49:01 +000047#include <rfb/Exception.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000048#include <network/TcpSocket.h>
DRC4426f002011-10-12 20:02:55 +000049#include <os/os.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000050
51#include <FL/Fl.H>
52#include <FL/Fl_Widget.H>
Pierre Ossman8eb35082012-03-27 12:50:54 +000053#include <FL/Fl_PNG_Image.H>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000054#include <FL/fl_ask.H>
Pierre Ossmanb8858222011-04-29 11:51:38 +000055#include <FL/x.H>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000056
57#include "i18n.h"
58#include "parameters.h"
59#include "CConn.h"
Pierre Ossman561ff0c2011-05-13 14:04:59 +000060#include "ServerDialog.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000061#include "UserDialog.h"
Adam Tkac8ac4b302013-01-23 13:55:46 +000062#include "vncviewer.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000063
DRCb65bb932011-06-24 03:17:00 +000064#ifdef WIN32
Pierre Ossman8eb35082012-03-27 12:50:54 +000065#include "resource.h"
DRCb65bb932011-06-24 03:17:00 +000066#include "win32.h"
67#endif
68
Pierre Ossman5156d5e2011-03-09 09:42:34 +000069rfb::LogWriter vlog("main");
70
71using namespace network;
72using namespace rfb;
73using namespace std;
74
Pierre Ossmanf52740e2012-04-25 15:43:56 +000075static const char aboutText[] = N_("TigerVNC Viewer %d-bit v%s (%s)\n"
76 "%s\n"
77 "Copyright (C) 1999-2011 TigerVNC Team and many others (see README.txt)\n"
78 "See http://www.tigervnc.org for information on TigerVNC.");
DRCd8e93dc2011-07-28 22:13:40 +000079extern const char* buildTime;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000080
Adam Tkac8ac4b302013-01-23 13:55:46 +000081char vncServerName[VNCSERVERNAMELEN] = { '\0' };
82
Pierre Ossman5156d5e2011-03-09 09:42:34 +000083static bool exitMainloop = false;
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000084static const char *exitError = NULL;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000085
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000086void exit_vncviewer(const char *error)
Pierre Ossman5156d5e2011-03-09 09:42:34 +000087{
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000088 // Prioritise the first error we get as that is probably the most
89 // relevant one.
90 if ((error != NULL) && (exitError == NULL))
91 exitError = strdup(error);
92
Pierre Ossman5156d5e2011-03-09 09:42:34 +000093 exitMainloop = true;
94}
95
Pierre Ossmanb8858222011-04-29 11:51:38 +000096void about_vncviewer()
97{
98 fl_message_title(_("About TigerVNC Viewer"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +000099 fl_message(gettext(aboutText), (int)sizeof(size_t)*8,
100 PACKAGE_VERSION, __BUILD__, buildTime);
Pierre Ossmanb8858222011-04-29 11:51:38 +0000101}
102
103static void about_callback(Fl_Widget *widget, void *data)
104{
105 about_vncviewer();
106}
107
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000108static void CleanupSignalHandler(int sig)
109{
110 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
111 // exit() rather than the default which is to abort.
112 vlog.info("CleanupSignalHandler called");
113 exit(1);
114}
115
116static void init_fltk()
117{
118 // Basic text size (10pt @ 96 dpi => 13px)
119 FL_NORMAL_SIZE = 13;
120
121#ifndef __APPLE__
122 // Select a FLTK scheme and background color that looks somewhat
123 // close to modern Linux and Windows.
124 Fl::scheme("gtk+");
125 Fl::background(220, 220, 220);
126#else
127 // On Mac OS X there is another scheme that fits better though.
128 Fl::scheme("plastic");
129#endif
130
Henrik Andersson3b837032011-09-19 13:46:55 +0000131 // Proper Gnome Shell integration requires that we set a sensible
132 // WM_CLASS for the window.
133 Fl_Window::default_xclass("vncviewer");
134
Pierre Ossman8eb35082012-03-27 12:50:54 +0000135 // Set the default icon for all windows.
136#ifdef HAVE_FLTK_ICONS
137#ifdef WIN32
138 HICON lg, sm;
139
140 lg = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
141 IMAGE_ICON, GetSystemMetrics(SM_CXICON),
142 GetSystemMetrics(SM_CYICON),
143 LR_DEFAULTCOLOR | LR_SHARED);
144 sm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
145 IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
146 GetSystemMetrics(SM_CYSMICON),
147 LR_DEFAULTCOLOR | LR_SHARED);
148
149 Fl_Window::default_icons(lg, sm);
150#elif ! defined(__APPLE__)
151 const int icon_sizes[] = {48, 32, 24, 16};
152
153 Fl_PNG_Image *icons[4];
154 int count;
155
156 count = 0;
157
158 // FIXME: Follow icon theme specification
159 for (size_t i = 0;i < sizeof(icon_sizes)/sizeof(icon_sizes[0]);i++) {
160 char icon_path[PATH_MAX];
161 bool exists;
162
Pierre Ossman6a007bd2012-09-11 10:56:21 +0000163 sprintf(icon_path, "%s/icons/hicolor/%dx%d/apps/tigervnc.png",
Pierre Ossman8eb35082012-03-27 12:50:54 +0000164 DATA_DIR, icon_sizes[i], icon_sizes[i]);
165
166#ifndef WIN32
167 struct stat st;
168 if (stat(icon_path, &st) != 0)
169#else
170 struct _stat st;
171 if (_stat(icon_path, &st) != 0)
172 return(false);
173#endif
174 exists = false;
175 else
176 exists = true;
177
178 if (exists) {
179 icons[count] = new Fl_PNG_Image(icon_path);
180 if (icons[count]->w() == 0 ||
181 icons[count]->h() == 0 ||
182 icons[count]->d() != 4) {
183 delete icons[count];
184 continue;
185 }
186
187 count++;
188 }
189 }
190
191 Fl_Window::default_icons((const Fl_RGB_Image**)icons, count);
192
193 for (int i = 0;i < count;i++)
194 delete icons[i];
195#endif
196#endif // FLTK_HAVE_ICONS
197
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000198 // This makes the "icon" in dialogs rounded, which fits better
199 // with the above schemes.
200 fl_message_icon()->box(FL_UP_BOX);
201
202 // Turn off the annoying behaviour where popups track the mouse.
203 fl_message_hotspot(false);
204
205 // Avoid empty titles for popups
206 fl_message_title_default(_("TigerVNC Viewer"));
207
208#ifdef WIN32
209 // Most "normal" Windows apps use this font for UI elements.
210 Fl::set_font(FL_HELVETICA, "Tahoma");
211#endif
212
213 // FLTK exposes these so that we can translate them.
214 fl_no = _("No");
215 fl_yes = _("Yes");
216 fl_ok = _("OK");
217 fl_cancel = _("Cancel");
218 fl_close = _("Close");
Pierre Ossmanb8858222011-04-29 11:51:38 +0000219
220#ifdef __APPLE__
Peter Åstrandb7c55242011-08-29 13:14:51 +0000221 /* Needs trailing space */
222 static char fltk_about[16];
223 snprintf(fltk_about, sizeof(fltk_about), "%s ", _("About"));
224 Fl_Mac_App_Menu::about = fltk_about;
225 static char fltk_hide[16];
226 snprintf(fltk_hide, sizeof(fltk_hide), "%s ", _("Hide"));
227 Fl_Mac_App_Menu::hide = fltk_hide;
228 static char fltk_quit[16];
229 snprintf(fltk_quit, sizeof(fltk_quit), "%s ", _("Quit"));
230 Fl_Mac_App_Menu::quit = fltk_quit;
231
Pierre Ossman41ba6032011-06-16 11:09:31 +0000232 Fl_Mac_App_Menu::print = ""; // Don't want the print item
233 Fl_Mac_App_Menu::services = _("Services");
Pierre Ossman41ba6032011-06-16 11:09:31 +0000234 Fl_Mac_App_Menu::hide_others = _("Hide Others");
235 Fl_Mac_App_Menu::show = _("Show All");
Pierre Ossman41ba6032011-06-16 11:09:31 +0000236
Pierre Ossmanb8858222011-04-29 11:51:38 +0000237 fl_mac_set_about(about_callback, NULL);
238#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000239}
240
241static void mkvnchomedir()
242{
243 // Create .vnc in the user's home directory if it doesn't already exist
244 char* homeDir = NULL;
245
246 if (getvnchomedir(&homeDir) == -1) {
247 vlog.error(_("Could not create VNC home directory: can't obtain home "
248 "directory path."));
249 } else {
250 int result = mkdir(homeDir, 0755);
251 if (result == -1 && errno != EEXIST)
252 vlog.error(_("Could not create VNC home directory: %s."), strerror(errno));
253 delete [] homeDir;
254 }
255}
256
257static void usage(const char *programName)
258{
259 fprintf(stderr,
260 "\nusage: %s [parameters] [host:displayNum] [parameters]\n"
261 " %s [parameters] -listen [port] [parameters]\n",
262 programName, programName);
263 fprintf(stderr,"\n"
264 "Parameters can be turned on with -<param> or off with -<param>=0\n"
265 "Parameters which take a value can be specified as "
266 "-<param> <value>\n"
267 "Other valid forms are <param>=<value> -<param>=<value> "
268 "--<param>=<value>\n"
269 "Parameter names are case-insensitive. The parameters are:\n\n");
270 Configuration::listParams(79, 14);
271 exit(1);
272}
273
Adam Tkac8ac4b302013-01-23 13:55:46 +0000274#ifndef WIN32
275static int
276interpretViaParam(char *remoteHost, int *remotePort, int localPort)
277{
278 const int SERVER_PORT_OFFSET = 5900;
279 char *pos = strchr(vncServerName, ':');
280 if (pos == NULL)
281 *remotePort = SERVER_PORT_OFFSET;
282 else {
283 int portOffset = SERVER_PORT_OFFSET;
284 size_t len;
285 *pos++ = '\0';
286 len = strlen(pos);
287 if (*pos == ':') {
288 /* Two colons is an absolute port number, not an offset. */
289 pos++;
290 len--;
291 portOffset = 0;
292 }
293 if (!len || strspn (pos, "-0123456789") != len )
294 return 1;
295 *remotePort = atoi(pos) + portOffset;
296 }
297
298 if (*vncServerName != '\0')
299 strncpy(remoteHost, vncServerName, VNCSERVERNAMELEN);
300 else
301 strncpy(remoteHost, "localhost", VNCSERVERNAMELEN);
302
303 remoteHost[VNCSERVERNAMELEN - 1] = '\0';
304
305 snprintf(vncServerName, VNCSERVERNAMELEN, "localhost::%d", localPort);
306 vncServerName[VNCSERVERNAMELEN - 1] = '\0';
307 vlog.error(vncServerName);
308
309 return 0;
310}
311
312static void
313createTunnel(const char *gatewayHost, const char *remoteHost,
314 int remotePort, int localPort)
315{
316 char *cmd = getenv("VNC_VIA_CMD");
317 char *percent;
318 char lport[10], rport[10];
319 sprintf(lport, "%d", localPort);
320 sprintf(rport, "%d", remotePort);
321 setenv("G", gatewayHost, 1);
322 setenv("H", remoteHost, 1);
323 setenv("R", rport, 1);
324 setenv("L", lport, 1);
Adam Tkac8ac4b302013-01-23 13:55:46 +0000325 if (!cmd)
326 cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20";
327 /* Compatibility with TigerVNC's method. */
328 while ((percent = strchr(cmd, '%')) != NULL)
329 *percent = '$';
330 system(cmd);
331}
332
333static int mktunnel()
334{
335 const char *gatewayHost;
336 char remoteHost[VNCSERVERNAMELEN];
337 int localPort = findFreeTcpPort();
338 int remotePort;
339
340 gatewayHost = strDup(via.getValueStr());
341 if (interpretViaParam(remoteHost, &remotePort, localPort) != 0)
342 return 1;
343 createTunnel(gatewayHost, remoteHost, remotePort, localPort);
344
345 return 0;
346}
347#endif /* !WIN32 */
348
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000349int main(int argc, char** argv)
350{
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000351 UserDialog dlg;
352
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000353 setlocale(LC_ALL, "");
Pierre Ossman0878eca2012-03-27 13:03:22 +0000354 bindtextdomain(PACKAGE_NAME, LOCALE_DIR);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000355 textdomain(PACKAGE_NAME);
356
357 rfb::SecurityClient::setDefaults();
358
359 // Write about text to console, still using normal locale codeset
Pierre Ossmanf52740e2012-04-25 15:43:56 +0000360 fprintf(stderr,"\n");
361 fprintf(stderr, gettext(aboutText), (int)sizeof(size_t)*8,
362 PACKAGE_VERSION, __BUILD__, buildTime);
363 fprintf(stderr,"\n");
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000364
365 // Set gettext codeset to what our GUI toolkit uses. Since we are
366 // passing strings from strerror/gai_strerror to the GUI, these must
367 // be in GUI codeset as well.
368 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
369 bind_textdomain_codeset("libc", "UTF-8");
370
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000371 rfb::initStdIOLoggers();
Pierre Ossmanc8719ad2012-04-26 14:27:52 +0000372#ifdef WIN32
373 rfb::initFileLogger("C:\\temp\\vncviewer.log");
374#else
375 rfb::initFileLogger("/tmp/vncviewer.log");
376#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000377 rfb::LogWriter::setLogParams("*:stderr:30");
378
379#ifdef SIGHUP
380 signal(SIGHUP, CleanupSignalHandler);
381#endif
382 signal(SIGINT, CleanupSignalHandler);
383 signal(SIGTERM, CleanupSignalHandler);
384
385 init_fltk();
386
387 Configuration::enableViewerParams();
388
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000389 /* Load the default parameter settings */
390 const char* defaultServerName;
391 try {
392 defaultServerName = loadViewerParameters(NULL);
393 } catch (rfb::Exception& e) {
394 fl_alert("%s", e.str());
395 }
396
DRC5aa06502011-06-23 22:04:46 +0000397 int i = 1;
398 if (!Fl::args(argc, argv, i) || i < argc)
399 for (; i < argc; i++) {
400 if (Configuration::setParam(argv[i]))
401 continue;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000402
DRC5aa06502011-06-23 22:04:46 +0000403 if (argv[i][0] == '-') {
404 if (i+1 < argc) {
405 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
406 i++;
407 continue;
408 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000409 }
DRC5aa06502011-06-23 22:04:46 +0000410 usage(argv[0]);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000411 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000412
Adam Tkac8ac4b302013-01-23 13:55:46 +0000413 strncpy(vncServerName, argv[i], VNCSERVERNAMELEN);
414 vncServerName[VNCSERVERNAMELEN - 1] = '\0';
DRC5aa06502011-06-23 22:04:46 +0000415 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000416
417 if (!::autoSelect.hasBeenSet()) {
418 // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000419 if (::preferredEncoding.hasBeenSet() || ::fullColour.hasBeenSet() ||
420 ::fullColourAlias.hasBeenSet()) {
421 ::autoSelect.setParam(false);
422 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000423 }
424 if (!::fullColour.hasBeenSet() && !::fullColourAlias.hasBeenSet()) {
425 // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
426 if (!::autoSelect && (::lowColourLevel.hasBeenSet() ||
427 ::lowColourLevelAlias.hasBeenSet())) {
428 ::fullColour.setParam(false);
429 }
430 }
431 if (!::customCompressLevel.hasBeenSet()) {
432 // Default to CustomCompressLevel=1 if CompressLevel is used.
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000433 if(::compressLevel.hasBeenSet()) {
434 ::customCompressLevel.setParam(true);
435 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000436 }
437
438 mkvnchomedir();
439
440 CSecurity::upg = &dlg;
441#ifdef HAVE_GNUTLS
442 CSecurityTLS::msg = &dlg;
443#endif
444
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000445 Socket *sock = NULL;
446
DRC3e7ed812013-02-26 10:34:22 +0000447#ifndef WIN32
Adam Tkac571089b2013-02-19 14:30:32 +0000448 /* Specifying -via and -listen together is nonsense */
449 if (listenMode && strlen(via.getValueStr()) > 0) {
450 vlog.error("Parameters -listen and -via are incompatible");
451 fl_alert("Parameters -listen and -via are incompatible");
452 exit_vncviewer();
453 return 1;
454 }
DRC3e7ed812013-02-26 10:34:22 +0000455#endif
Adam Tkac571089b2013-02-19 14:30:32 +0000456
457 if (listenMode) {
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000458 try {
459 int port = 5500;
460 if (isdigit(vncServerName[0]))
461 port = atoi(vncServerName);
462
463 TcpListener listener(NULL, port);
464
465 vlog.info("Listening on port %d\n", port);
466 sock = listener.accept();
467 } catch (rdr::Exception& e) {
468 vlog.error("%s", e.str());
469 fl_alert("%s", e.str());
470 exit_vncviewer();
471 return 1;
472 }
473
474 } else {
475 if (vncServerName[0] == '\0') {
476 ServerDialog::run(defaultServerName, vncServerName);
477 if (vncServerName[0] == '\0')
478 return 1;
479 }
Pierre Ossman561ff0c2011-05-13 14:04:59 +0000480
Adam Tkac8ac4b302013-01-23 13:55:46 +0000481#ifndef WIN32
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000482 if (strlen (via.getValueStr()) > 0 && mktunnel() != 0)
483 usage(argv[0]);
Adam Tkac8ac4b302013-01-23 13:55:46 +0000484#endif
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000485 }
Adam Tkac8ac4b302013-01-23 13:55:46 +0000486
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000487 CConn *cc = new CConn(vncServerName, sock);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000488
489 while (!exitMainloop) {
490 int next_timer;
491
492 next_timer = Timer::checkTimeouts();
493 if (next_timer == 0)
494 next_timer = INT_MAX;
495
496 if (Fl::wait((double)next_timer / 1000.0) < 0.0) {
497 vlog.error(_("Internal FLTK error. Exiting."));
498 break;
499 }
500 }
501
Pierre Ossmane2ef5c12011-07-12 16:56:34 +0000502 delete cc;
503
504 if (exitError != NULL)
Pierre Ossmanf52740e2012-04-25 15:43:56 +0000505 fl_alert("%s", exitError);
Pierre Ossmane2ef5c12011-07-12 16:56:34 +0000506
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000507 return 0;
508}