blob: 569ee4a0f3c4abf65a5e4f7eea2d5d561f975d81 [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>
27#include <stdlib.h>
28#include <errno.h>
29#include <signal.h>
30#include <locale.h>
31#include <sys/stat.h>
32
33#ifdef WIN32
34#include <direct.h>
35#define mkdir(path, mode) _mkdir(path)
36#endif
37
Pierre Ossman5156d5e2011-03-09 09:42:34 +000038#include <rfb/Logger_stdio.h>
39#include <rfb/SecurityClient.h>
40#include <rfb/Security.h>
41#ifdef HAVE_GNUTLS
42#include <rfb/CSecurityTLS.h>
43#endif
44#include <rfb/LogWriter.h>
45#include <rfb/Timer.h>
Peter Åstrand8a2b0812012-08-08 11:49:01 +000046#include <rfb/Exception.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000047#include <network/TcpSocket.h>
DRC4426f002011-10-12 20:02:55 +000048#include <os/os.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000049
50#include <FL/Fl.H>
51#include <FL/Fl_Widget.H>
Pierre Ossman8eb35082012-03-27 12:50:54 +000052#include <FL/Fl_PNG_Image.H>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000053#include <FL/fl_ask.H>
Pierre Ossmanb8858222011-04-29 11:51:38 +000054#include <FL/x.H>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000055
56#include "i18n.h"
57#include "parameters.h"
58#include "CConn.h"
Pierre Ossman561ff0c2011-05-13 14:04:59 +000059#include "ServerDialog.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000060#include "UserDialog.h"
Adam Tkac8ac4b302013-01-23 13:55:46 +000061#include "vncviewer.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000062
DRCb65bb932011-06-24 03:17:00 +000063#ifdef WIN32
Pierre Ossman8eb35082012-03-27 12:50:54 +000064#include "resource.h"
DRCb65bb932011-06-24 03:17:00 +000065#include "win32.h"
66#endif
67
Pierre Ossman5156d5e2011-03-09 09:42:34 +000068rfb::LogWriter vlog("main");
69
70using namespace network;
71using namespace rfb;
72using namespace std;
73
Pierre Ossmanf52740e2012-04-25 15:43:56 +000074static const char aboutText[] = N_("TigerVNC Viewer %d-bit v%s (%s)\n"
75 "%s\n"
76 "Copyright (C) 1999-2011 TigerVNC Team and many others (see README.txt)\n"
77 "See http://www.tigervnc.org for information on TigerVNC.");
DRCd8e93dc2011-07-28 22:13:40 +000078extern const char* buildTime;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000079
Adam Tkac8ac4b302013-01-23 13:55:46 +000080char vncServerName[VNCSERVERNAMELEN] = { '\0' };
81
Pierre Ossman5156d5e2011-03-09 09:42:34 +000082static bool exitMainloop = false;
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000083static const char *exitError = NULL;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000084
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000085void exit_vncviewer(const char *error)
Pierre Ossman5156d5e2011-03-09 09:42:34 +000086{
Pierre Ossmane2ef5c12011-07-12 16:56:34 +000087 // Prioritise the first error we get as that is probably the most
88 // relevant one.
89 if ((error != NULL) && (exitError == NULL))
90 exitError = strdup(error);
91
Pierre Ossman5156d5e2011-03-09 09:42:34 +000092 exitMainloop = true;
93}
94
Pierre Ossmanb8858222011-04-29 11:51:38 +000095void about_vncviewer()
96{
97 fl_message_title(_("About TigerVNC Viewer"));
Pierre Ossmanf52740e2012-04-25 15:43:56 +000098 fl_message(gettext(aboutText), (int)sizeof(size_t)*8,
99 PACKAGE_VERSION, __BUILD__, buildTime);
Pierre Ossmanb8858222011-04-29 11:51:38 +0000100}
101
102static void about_callback(Fl_Widget *widget, void *data)
103{
104 about_vncviewer();
105}
106
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000107static void CleanupSignalHandler(int sig)
108{
109 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
110 // exit() rather than the default which is to abort.
111 vlog.info("CleanupSignalHandler called");
112 exit(1);
113}
114
115static void init_fltk()
116{
117 // Basic text size (10pt @ 96 dpi => 13px)
118 FL_NORMAL_SIZE = 13;
119
120#ifndef __APPLE__
121 // Select a FLTK scheme and background color that looks somewhat
122 // close to modern Linux and Windows.
123 Fl::scheme("gtk+");
124 Fl::background(220, 220, 220);
125#else
126 // On Mac OS X there is another scheme that fits better though.
127 Fl::scheme("plastic");
128#endif
129
Henrik Andersson3b837032011-09-19 13:46:55 +0000130 // Proper Gnome Shell integration requires that we set a sensible
131 // WM_CLASS for the window.
132 Fl_Window::default_xclass("vncviewer");
133
Pierre Ossman8eb35082012-03-27 12:50:54 +0000134 // Set the default icon for all windows.
135#ifdef HAVE_FLTK_ICONS
136#ifdef WIN32
137 HICON lg, sm;
138
139 lg = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
140 IMAGE_ICON, GetSystemMetrics(SM_CXICON),
141 GetSystemMetrics(SM_CYICON),
142 LR_DEFAULTCOLOR | LR_SHARED);
143 sm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
144 IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
145 GetSystemMetrics(SM_CYSMICON),
146 LR_DEFAULTCOLOR | LR_SHARED);
147
148 Fl_Window::default_icons(lg, sm);
149#elif ! defined(__APPLE__)
150 const int icon_sizes[] = {48, 32, 24, 16};
151
152 Fl_PNG_Image *icons[4];
153 int count;
154
155 count = 0;
156
157 // FIXME: Follow icon theme specification
158 for (size_t i = 0;i < sizeof(icon_sizes)/sizeof(icon_sizes[0]);i++) {
159 char icon_path[PATH_MAX];
160 bool exists;
161
Pierre Ossman6a007bd2012-09-11 10:56:21 +0000162 sprintf(icon_path, "%s/icons/hicolor/%dx%d/apps/tigervnc.png",
Pierre Ossman8eb35082012-03-27 12:50:54 +0000163 DATA_DIR, icon_sizes[i], icon_sizes[i]);
164
165#ifndef WIN32
166 struct stat st;
167 if (stat(icon_path, &st) != 0)
168#else
169 struct _stat st;
170 if (_stat(icon_path, &st) != 0)
171 return(false);
172#endif
173 exists = false;
174 else
175 exists = true;
176
177 if (exists) {
178 icons[count] = new Fl_PNG_Image(icon_path);
179 if (icons[count]->w() == 0 ||
180 icons[count]->h() == 0 ||
181 icons[count]->d() != 4) {
182 delete icons[count];
183 continue;
184 }
185
186 count++;
187 }
188 }
189
190 Fl_Window::default_icons((const Fl_RGB_Image**)icons, count);
191
192 for (int i = 0;i < count;i++)
193 delete icons[i];
194#endif
195#endif // FLTK_HAVE_ICONS
196
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000197 // This makes the "icon" in dialogs rounded, which fits better
198 // with the above schemes.
199 fl_message_icon()->box(FL_UP_BOX);
200
201 // Turn off the annoying behaviour where popups track the mouse.
202 fl_message_hotspot(false);
203
204 // Avoid empty titles for popups
205 fl_message_title_default(_("TigerVNC Viewer"));
206
207#ifdef WIN32
208 // Most "normal" Windows apps use this font for UI elements.
209 Fl::set_font(FL_HELVETICA, "Tahoma");
210#endif
211
212 // FLTK exposes these so that we can translate them.
213 fl_no = _("No");
214 fl_yes = _("Yes");
215 fl_ok = _("OK");
216 fl_cancel = _("Cancel");
217 fl_close = _("Close");
Pierre Ossmanb8858222011-04-29 11:51:38 +0000218
219#ifdef __APPLE__
Peter Åstrandb7c55242011-08-29 13:14:51 +0000220 /* Needs trailing space */
221 static char fltk_about[16];
222 snprintf(fltk_about, sizeof(fltk_about), "%s ", _("About"));
223 Fl_Mac_App_Menu::about = fltk_about;
224 static char fltk_hide[16];
225 snprintf(fltk_hide, sizeof(fltk_hide), "%s ", _("Hide"));
226 Fl_Mac_App_Menu::hide = fltk_hide;
227 static char fltk_quit[16];
228 snprintf(fltk_quit, sizeof(fltk_quit), "%s ", _("Quit"));
229 Fl_Mac_App_Menu::quit = fltk_quit;
230
Pierre Ossman41ba6032011-06-16 11:09:31 +0000231 Fl_Mac_App_Menu::print = ""; // Don't want the print item
232 Fl_Mac_App_Menu::services = _("Services");
Pierre Ossman41ba6032011-06-16 11:09:31 +0000233 Fl_Mac_App_Menu::hide_others = _("Hide Others");
234 Fl_Mac_App_Menu::show = _("Show All");
Pierre Ossman41ba6032011-06-16 11:09:31 +0000235
Pierre Ossmanb8858222011-04-29 11:51:38 +0000236 fl_mac_set_about(about_callback, NULL);
237#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000238}
239
240static void mkvnchomedir()
241{
242 // Create .vnc in the user's home directory if it doesn't already exist
243 char* homeDir = NULL;
244
245 if (getvnchomedir(&homeDir) == -1) {
246 vlog.error(_("Could not create VNC home directory: can't obtain home "
247 "directory path."));
248 } else {
249 int result = mkdir(homeDir, 0755);
250 if (result == -1 && errno != EEXIST)
251 vlog.error(_("Could not create VNC home directory: %s."), strerror(errno));
252 delete [] homeDir;
253 }
254}
255
256static void usage(const char *programName)
257{
258 fprintf(stderr,
259 "\nusage: %s [parameters] [host:displayNum] [parameters]\n"
260 " %s [parameters] -listen [port] [parameters]\n",
261 programName, programName);
262 fprintf(stderr,"\n"
263 "Parameters can be turned on with -<param> or off with -<param>=0\n"
264 "Parameters which take a value can be specified as "
265 "-<param> <value>\n"
266 "Other valid forms are <param>=<value> -<param>=<value> "
267 "--<param>=<value>\n"
268 "Parameter names are case-insensitive. The parameters are:\n\n");
269 Configuration::listParams(79, 14);
270 exit(1);
271}
272
Adam Tkac8ac4b302013-01-23 13:55:46 +0000273#ifndef WIN32
274static int
275interpretViaParam(char *remoteHost, int *remotePort, int localPort)
276{
277 const int SERVER_PORT_OFFSET = 5900;
278 char *pos = strchr(vncServerName, ':');
279 if (pos == NULL)
280 *remotePort = SERVER_PORT_OFFSET;
281 else {
282 int portOffset = SERVER_PORT_OFFSET;
283 size_t len;
284 *pos++ = '\0';
285 len = strlen(pos);
286 if (*pos == ':') {
287 /* Two colons is an absolute port number, not an offset. */
288 pos++;
289 len--;
290 portOffset = 0;
291 }
292 if (!len || strspn (pos, "-0123456789") != len )
293 return 1;
294 *remotePort = atoi(pos) + portOffset;
295 }
296
297 if (*vncServerName != '\0')
298 strncpy(remoteHost, vncServerName, VNCSERVERNAMELEN);
299 else
300 strncpy(remoteHost, "localhost", VNCSERVERNAMELEN);
301
302 remoteHost[VNCSERVERNAMELEN - 1] = '\0';
303
304 snprintf(vncServerName, VNCSERVERNAMELEN, "localhost::%d", localPort);
305 vncServerName[VNCSERVERNAMELEN - 1] = '\0';
306 vlog.error(vncServerName);
307
308 return 0;
309}
310
311static void
312createTunnel(const char *gatewayHost, const char *remoteHost,
313 int remotePort, int localPort)
314{
315 char *cmd = getenv("VNC_VIA_CMD");
316 char *percent;
317 char lport[10], rport[10];
318 sprintf(lport, "%d", localPort);
319 sprintf(rport, "%d", remotePort);
320 setenv("G", gatewayHost, 1);
321 setenv("H", remoteHost, 1);
322 setenv("R", rport, 1);
323 setenv("L", lport, 1);
Adam Tkac8ac4b302013-01-23 13:55:46 +0000324 if (!cmd)
325 cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20";
326 /* Compatibility with TigerVNC's method. */
327 while ((percent = strchr(cmd, '%')) != NULL)
328 *percent = '$';
329 system(cmd);
330}
331
332static int mktunnel()
333{
334 const char *gatewayHost;
335 char remoteHost[VNCSERVERNAMELEN];
336 int localPort = findFreeTcpPort();
337 int remotePort;
338
339 gatewayHost = strDup(via.getValueStr());
340 if (interpretViaParam(remoteHost, &remotePort, localPort) != 0)
341 return 1;
342 createTunnel(gatewayHost, remoteHost, remotePort, localPort);
343
344 return 0;
345}
346#endif /* !WIN32 */
347
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000348int main(int argc, char** argv)
349{
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000350 UserDialog dlg;
351
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000352 setlocale(LC_ALL, "");
Pierre Ossman0878eca2012-03-27 13:03:22 +0000353 bindtextdomain(PACKAGE_NAME, LOCALE_DIR);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000354 textdomain(PACKAGE_NAME);
355
356 rfb::SecurityClient::setDefaults();
357
358 // Write about text to console, still using normal locale codeset
Pierre Ossmanf52740e2012-04-25 15:43:56 +0000359 fprintf(stderr,"\n");
360 fprintf(stderr, gettext(aboutText), (int)sizeof(size_t)*8,
361 PACKAGE_VERSION, __BUILD__, buildTime);
362 fprintf(stderr,"\n");
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000363
364 // Set gettext codeset to what our GUI toolkit uses. Since we are
365 // passing strings from strerror/gai_strerror to the GUI, these must
366 // be in GUI codeset as well.
367 bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
368 bind_textdomain_codeset("libc", "UTF-8");
369
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000370 rfb::initStdIOLoggers();
Pierre Ossmanc8719ad2012-04-26 14:27:52 +0000371#ifdef WIN32
372 rfb::initFileLogger("C:\\temp\\vncviewer.log");
373#else
374 rfb::initFileLogger("/tmp/vncviewer.log");
375#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000376 rfb::LogWriter::setLogParams("*:stderr:30");
377
378#ifdef SIGHUP
379 signal(SIGHUP, CleanupSignalHandler);
380#endif
381 signal(SIGINT, CleanupSignalHandler);
382 signal(SIGTERM, CleanupSignalHandler);
383
384 init_fltk();
385
386 Configuration::enableViewerParams();
387
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000388 /* Load the default parameter settings */
389 const char* defaultServerName;
390 try {
391 defaultServerName = loadViewerParameters(NULL);
392 } catch (rfb::Exception& e) {
393 fl_alert("%s", e.str());
394 }
395
DRC5aa06502011-06-23 22:04:46 +0000396 int i = 1;
397 if (!Fl::args(argc, argv, i) || i < argc)
398 for (; i < argc; i++) {
399 if (Configuration::setParam(argv[i]))
400 continue;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000401
DRC5aa06502011-06-23 22:04:46 +0000402 if (argv[i][0] == '-') {
403 if (i+1 < argc) {
404 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
405 i++;
406 continue;
407 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000408 }
DRC5aa06502011-06-23 22:04:46 +0000409 usage(argv[0]);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000410 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000411
Adam Tkac8ac4b302013-01-23 13:55:46 +0000412 strncpy(vncServerName, argv[i], VNCSERVERNAMELEN);
413 vncServerName[VNCSERVERNAMELEN - 1] = '\0';
DRC5aa06502011-06-23 22:04:46 +0000414 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000415
416 if (!::autoSelect.hasBeenSet()) {
417 // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000418 if (::preferredEncoding.hasBeenSet() || ::fullColour.hasBeenSet() ||
419 ::fullColourAlias.hasBeenSet()) {
420 ::autoSelect.setParam(false);
421 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000422 }
423 if (!::fullColour.hasBeenSet() && !::fullColourAlias.hasBeenSet()) {
424 // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
425 if (!::autoSelect && (::lowColourLevel.hasBeenSet() ||
426 ::lowColourLevelAlias.hasBeenSet())) {
427 ::fullColour.setParam(false);
428 }
429 }
430 if (!::customCompressLevel.hasBeenSet()) {
431 // Default to CustomCompressLevel=1 if CompressLevel is used.
Peter Åstrand8a2b0812012-08-08 11:49:01 +0000432 if(::compressLevel.hasBeenSet()) {
433 ::customCompressLevel.setParam(true);
434 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000435 }
436
437 mkvnchomedir();
438
439 CSecurity::upg = &dlg;
440#ifdef HAVE_GNUTLS
441 CSecurityTLS::msg = &dlg;
442#endif
443
Adam Tkac8ac4b302013-01-23 13:55:46 +0000444 if (vncServerName[0] == '\0') {
445 ServerDialog::run(defaultServerName, vncServerName);
446 if (vncServerName[0] == '\0')
Pierre Ossman561ff0c2011-05-13 14:04:59 +0000447 return 1;
448 }
449
Adam Tkac8ac4b302013-01-23 13:55:46 +0000450#ifndef WIN32
451 if (strlen (via.getValueStr()) > 0 && mktunnel() != 0)
452 usage(argv[0]);
453#endif
454
Pierre Ossmane2ef5c12011-07-12 16:56:34 +0000455 CConn *cc = new CConn(vncServerName);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000456
457 while (!exitMainloop) {
458 int next_timer;
459
460 next_timer = Timer::checkTimeouts();
461 if (next_timer == 0)
462 next_timer = INT_MAX;
463
464 if (Fl::wait((double)next_timer / 1000.0) < 0.0) {
465 vlog.error(_("Internal FLTK error. Exiting."));
466 break;
467 }
468 }
469
Pierre Ossmane2ef5c12011-07-12 16:56:34 +0000470 delete cc;
471
472 if (exitError != NULL)
Pierre Ossmanf52740e2012-04-25 15:43:56 +0000473 fl_alert("%s", exitError);
Pierre Ossmane2ef5c12011-07-12 16:56:34 +0000474
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000475 return 0;
476}