/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
 * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
 * Copyright (C) 2015-2025 m-privacy GmbH
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <queue>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <locale.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#ifdef WIN32
#include <os/winerrno.h>
#include <winsock2.h>
#include <windows.h>
#include <tchar.h>
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#include <wtypes.h>
#include <dbghelp.h>
#include <versionhelpers.h>
#else
#include <execinfo.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/utsname.h>
#endif

#ifdef __APPLE__
#include <Carbon/Carbon.h>
#endif

#if !defined(WIN32) && !defined(__APPLE__)
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#endif

#include <rfb/Logger_stdio.h>
#ifndef WIN32
#include <rfb/Logger_syslog.h>
#endif
#include <rfb/SecurityClient.h>
#include <rfb/Security.h>
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#include <rfb/LogWriter.h>
#include <rfb/Timer.h>
#include <rfb/Exception.h>
#include <rfb/CConnection.h>
#include <rfb/CMsgWriter.h>
#include <rfb/CWebcamHandler.h>
#include <rfb/UserMsgBox.h>
#include <network/TcpSocket.h>
#include <os/os.h>

#include <FL/Fl.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_PNG_Image.H>
#include <FL/Fl_Sys_Menu_Bar.H>
#include <FL/fl_ask.H>
#include <FL/x.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Return_Button.H>

#include "i18n.h"
#include "parameters.h"
#include "CConn.h"
#include "ServerDialog.h"
#include "UserDialog.h"
#include "touch.h"
#include "vncviewer.h"
#include "fltk_layout.h"
#include "CAutotransferHandler.h"
#include "NormalEnterDialog.h"

#ifdef WIN32
#include "resource.h"
#include "win32.h"
#endif

#include <tgpro_environment.h>
#include "tgpro_helpers.h"
#include "ConnectingMessage.h"
#include "printing.h"
#include "sound_handler.h"
#include "NativeMessagingApp.h"
#include "MagicUrlHelper.h"
#include "mp_utils.h"
#include "dpi_scale.h"

rfb::LogWriter vlog("main");

using namespace network;
using namespace rfb;
using namespace std;

StringParameter initialURL("InitialURL", "Open URL after connecting", "");
AliasParameter url("url", "Alias for InitialURL", &initialURL);
#ifndef WIN32
StringParameter logFile("LogFile", "Output file for file log target", "/tmp/vncviewer.log");
#endif
static const char _aboutText[] = N_("%s %s Viewer %d-bit v%s (%s)\n"
                                   "Copyright (C) 1999-2025 TigerVNC Team, m-privacy GmbH and many others (see README.txt)\n");
static char aboutText[1024];

char vncServerName[VNCSERVERNAMELEN] = { '\0' };

static const char *argv0 = NULL;

static bool inMainloop = false;
static bool exitMainloop = false;
static const char *exitError = NULL;

static ConnectingMessage* wait_window = NULL;
static std::queue<char*> autotransferProcessingQueue;

#ifdef __APPLE__
void * _Unwind_Resume =0;
#endif

float g_scale = 1.0f; // Will default to 1. Windows >=10 will try to change it. Linux won't, for now.

const char *about_text()
{
  static char buffer[1024];

  // This is used in multiple places with potentially different
  // encodings, so we need to make sure we get a fresh string every
  // time.
  snprintf(buffer, sizeof(buffer),
           _("%s %s %d-bit v%s\n"
             "Built on: %s\n"
             "Copyright (C) 1999-%d TigerVNC Team, m-privacy GmbH and many others (see README.rst)\n"),
           vendorName.getData(), productName.getData(),
           (int)sizeof(size_t)*8, PACKAGE_VERSION,
           BUILD_TIMESTAMP, 2026);

  return buffer;
}

void exit_vncviewer(const char *error, ...)
{
  // Prioritise the first error we get as that is probably the most
  // relevant one.
  if ((error != NULL) && (exitError == NULL)) {
    va_list ap;

    va_start(ap, error);
    exitError = (char*)malloc(1024);
    if (exitError)
      (void) vsnprintf((char*)exitError, 1024, error, ap);
    va_end(ap);
  }

  if (inMainloop)
    exitMainloop = true;
  else {
    // We're early in the startup. Assume we can just exit().
    if (alertOnFatalError && (exitError != NULL))
      fl_alert("%s", exitError);
    exit(EXIT_FAILURE);
  }

#if defined(WIN32) || defined(WIN64)
  vlog.debug("Removing magicurl temp file '%s'", get_tmp_magicurl_pipe_name_file_path());
  remove(get_tmp_magicurl_pipe_name_file_path());
#endif /* defined(WIN32) || defined(WIN64) */
  CWebcamHandler::killAllFfmpegProcesses(); // In the highly improbable case of the user running a ffmpeg binary herself, it will unfortunately be killed too.
}

bool should_exit()
{
  return exitMainloop;
}

void about_vncviewer()
{
  char about_title[256];
  snprintf(about_title, 256, _("About the %s Viewer"), productName.getData());
  fl_message_title(about_title);
  fl_message("%s", aboutText);
}

static void CleanupSignalHandler(int sig)
{
  // CleanupSignalHandler allows C++ object cleanup to happen because it calls
  // exit() rather than the default which is to abort.
  vlog.info("Termination signal %d has been received. %s Viewer will now exit.", sig, productName.getData());
  exit(1);
}

#if defined(WIN32) || defined(WIN64)
static void SegvSignalHandler(int sig) {
	void *array[20];
	unsigned short size;
	int fd;
	unsigned int i;
	SYMBOL_INFO * symbol;
	HANDLE process;

	vlog.info("SegvSignalHandler() called");

	fd = open(get_viewer_trace_file_path(), O_WRONLY | O_APPEND | O_CREAT | O_TRUNC, S_IRUSR|S_IWUSR);
	if (fd >= 0) {
		FILE * file;

		file = fdopen(fd, "a");
		process = GetCurrentProcess();
		SymInitialize(process, NULL, TRUE);
		size = CaptureStackBackTrace(0, 20, array, NULL);
		symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
		symbol->MaxNameLen   = 255;
		symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
		for( i = 0; i < size; i++ ) {
			SymFromAddr(process, (DWORD64)(array[i]), 0, symbol);
			fprintf(file, "%i: %s - 0x%0lX\n", size - i - 1, symbol->Name, (long unsigned int) symbol->Address);
		}
		free(symbol);
		close(fd);
	}
}

#else

static bool logEnabled = false;

static void Usr1SignalHandler(int sig) {
	if (!logEnabled) {
		vlog.info("Usr1SignalHandler(): enable file logging at level 100");
		rfb::LogWriter::setLogParams("*:file:100");
		logEnabled = true;
		vlog.info("Usr1SignalHandler(): enabled file logging at level 100");
	} else {
		vlog.info("Usr1SignalHandler(): disable file logging, reset to stderr at level 30");
		rfb::LogWriter::setLogParams("*:stderr:30");
		logEnabled = false;
		vlog.info("Usr1SignalHandler(): disabled file logging, reset to stderr at level 30");
	}
}

static void Usr2SignalHandler(int sig) {
	void *array[20];
	size_t size;
	int fd;

	vlog.info("Usr1SignalHandler() called");
	size = backtrace(array, 20);

	backtrace_symbols_fd(array, size, STDERR_FILENO);
	fd = open("/tmp/vncviewer-trace.log", O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW | O_TRUNC, S_IRUSR|S_IWUSR);
	if (fd >= 0) {
		backtrace_symbols_fd(array, size, fd);
		close(fd);
	}
}

static void SegvSignalHandler(int sig) {
	void *array[20];
	size_t size;
	int fd;

	vlog.info("SegvSignalHandler() called");
	size = backtrace(array, 20);

	fprintf(stderr, "Error: signal %i:\n", sig);
	backtrace_symbols_fd(array, size, STDERR_FILENO);
	fd = open("/tmp/vncviewer-trace.log", O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW | O_TRUNC, S_IRUSR|S_IWUSR);
	if (fd >= 0) {
		backtrace_symbols_fd(array, size, fd);
		close(fd);
	}
	exit(1);
}

static int getpulsecookie(void **cookie, size_t *size)
{
	char* path = getenv("PULSE_COOKIE");
	struct stat statbuf;
	bool needfree = false;

	if (!path || !path[0] || stat(path, &statbuf) || !statbuf.st_size) {
		char* homedir = getenv("HOME");
		if (!homedir) {
			const uid_t uid = getuid();
			const struct passwd* passwd = getpwuid(uid);
			if (!passwd) {
				return -1;
			}
			homedir = passwd->pw_dir;
		}

		const size_t len = strlen(homedir);
		path = (char *) malloc(len + 23);
		if (!path)
			return -1;
		memcpy(path, homedir, len);
		memcpy(path + len, "/.config/pulse/cookie\0", 22);

		if (stat(path, &statbuf) || !statbuf.st_size) {
			memcpy(path + len, "/.pulse-cookie\0", 15);
			if (stat(path, &statbuf) || !statbuf.st_size) {
				free(path);
				return -1;
			}
		}
		needfree = true;
	}
	*size = statbuf.st_size;
	*cookie = malloc(*size);
	if (!*cookie) {
		if (needfree)
			free(path);
		return -1;
	}
	int fd = open(path, O_RDONLY);
	if (fd < 0) {
		free(*cookie);
		*cookie = NULL;
		if (needfree)
			free(path);
		return -1;
	}
	ssize_t res = read(fd, *cookie, *size);
	close(fd);
	if (res < (ssize_t) *size) {
		free(*cookie);
		*cookie = NULL;
		*size = 0;
		if (needfree)
			free(path);
		return -1;
	}
	vlog.debug("getpulsecookie: read pulseaudio cookie from %s", path);

	if (needfree)
		free(path);
	return 0;
}
#endif

void run_mainloop(CConn *cc)
{
  int next_timer;

  if (cc && cc->state() == rfb::CConnection::RFBSTATE_INITIALISATION) {
    const char * url = initialURL.getData();
    if (url && url[0]) {
      if (cc->writer()) {
        vlog.debug("state INITIALISATION: send initial URL %s", url);
        if (!cc->writer()->sendInitialURL(url)) {
          vlog.debug("sendInitialURL(%s) failed", url);
        }
      } else {
        vlog.debug("state INITIALISATION: Cannot send initial URL %s, no writer", url);
      }
    }
  }
  if (cc && wait_window)
    if (cc->state() == rfb::CConnection::RFBSTATE_NORMAL) {
      delete wait_window;
      wait_window = NULL;
      const char * url = initialURL.getData();
      if (url && url[0]) {
        if (cc->writer()) {
          vlog.debug("Send initial URL %s", url);
          if (!cc->writer()->sendInitialURL(url)) {
            vlog.debug("sendInitialURL(%s) failed", url);
          }
        } else {
          vlog.debug("Cannot send initial URL %s, no writer", url);
        }
      }
      rdr::U16 value = mpScaling;
      if (value > 0) {
        vlog.debug("Send mpScaling value %u", value);
        if (!cc->writer()->sendSignal(MPSCALING_SIGNAL_ID, &value, 2))
          vlog.debug("sendSignal(MPSCALING_SIGNAL_ID) failed");
      } else {
        vlog.debug("Do not send mpScaling value 0");
      }

#ifndef WIN32
      void * pacookie;
      size_t cookiesize;

      if (cc->writer()) {
        if (!getpulsecookie(&pacookie, &cookiesize)) {
          vlog.debug("Send pulseaudio cookie");
          if (!cc->writer()->sendSignal(PACOOKIE_SIGNAL_ID, pacookie, cookiesize))
            vlog.debug("sendSignal(PACOOKIE_SIGNAL_ID) failed");
          free(pacookie);
        } else {
          vlog.debug("Cannot read local pulseaudio cookie, not sending");
        }
      } else
        vlog.debug("Cannot send pulseaudio cookie, no writer");
#endif
      cc->sendOptions();
    }

  next_timer = Timer::checkTimeouts();
  if (next_timer == 0)
    next_timer = INT_MAX;

  if (Fl::wait((double)next_timer / 1000.0) < 0.0) {
    vlog.error("Internal FLTK error. Exiting.");
    exit(-1);
  }

  /* Process autotransfer files if any at all: one at a time for every mainloop run */
  if (CAutotransferHandler::initCorrect) { // Actually, if it was initialized at all...
    char* autotransferFile = NULL;
    MUTEX_LOCK(&(CAutotransferHandler::processingQueueLock));
    if (!CAutotransferHandler::processingQueue.empty()) {
      autotransferFile = CAutotransferHandler::processingQueue.front();
      CAutotransferHandler::processingQueue.pop();
    }
    MUTEX_UNLOCK(&(CAutotransferHandler::processingQueueLock));
    if (autotransferFile) {
      vlog.debug("%s: processing file (moving to final autotransfer destination) with CAutotransferHandler", autotransferFile);
      CAutotransferHandler::handle(autotransferFile);
      free(autotransferFile);
    }
  }
}

#ifdef __APPLE__
static void about_callback(Fl_Widget *widget, void *data)
{
  about_vncviewer();
}

static void new_connection_cb(Fl_Widget *widget, void *data)
{
  const char *argv[2];
  pid_t pid;

  pid = fork();
  if (pid == -1) {
    vlog.error("Error starting new TigerVNC Viewer: %s", strerror(errno));
    return;
  }

  if (pid != 0)
    return;

  argv[0] = argv0;
  argv[1] = NULL;

  execvp(argv[0], (char * const *)argv);

  vlog.error("Error starting new TigerVNC Viewer: %s", strerror(errno));
  _exit(1);
}
#endif

static const char* getlocaledir()
{
#if defined(WIN32)
  static char localebuf[PATH_MAX];
  char *slash;

  GetModuleFileName(NULL, localebuf, sizeof(localebuf));

  slash = strrchr(localebuf, '\\');
  if (slash == NULL)
    return NULL;

  *slash = '\0';

  if ((strlen(localebuf) + strlen("\\locale")) >= sizeof(localebuf))
    return NULL;

  strcat(localebuf, "\\locale");

  return localebuf;
#elif defined(__APPLE__)
  CFBundleRef bundle;
  CFURLRef localeurl;
  CFStringRef localestr;
  Boolean ret;

  static char localebuf[PATH_MAX];

  bundle = CFBundleGetMainBundle();
  if (bundle == NULL)
    return NULL;

  localeurl = CFBundleCopyResourceURL(bundle, CFSTR("locale"),
                                      NULL, NULL);
  if (localeurl == NULL)
    return NULL;

  localestr = CFURLCopyFileSystemPath(localeurl, kCFURLPOSIXPathStyle);

  CFRelease(localeurl);

  ret = CFStringGetCString(localestr, localebuf, sizeof(localebuf),
                           kCFStringEncodingUTF8);
  if (!ret)
    return NULL;

  return localebuf;
#else
  return "/usr/share/locale";
#endif
}
static void init_fltk()
{
  // Basic text size (10pt @ 96 dpi => 13px)
  FL_NORMAL_SIZE = 13;

  // Select a FLTK scheme and background color that looks somewhat
  // close to modern systems
  Fl::scheme("gtk+");
  Fl::background(220, 220, 220);

  // macOS has a slightly brighter default background though
#ifdef __APPLE__
  Fl::background(240, 240, 240);
#endif

  // Proper Gnome Shell integration requires that we set a sensible
  // WM_CLASS for the window.
  Fl_Window::default_xclass("vncviewer");

  // Set the default icon for all windows.
#ifdef WIN32
  HICON lg, sm;

  lg = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
                        IMAGE_ICON, GetSystemMetrics(SM_CXICON),
                        GetSystemMetrics(SM_CYICON),
                        LR_DEFAULTCOLOR | LR_SHARED);
  sm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON),
                        IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
                        GetSystemMetrics(SM_CYSMICON),
                        LR_DEFAULTCOLOR | LR_SHARED);

  Fl_Window::default_icons(lg, sm);
#elif ! defined(__APPLE__)
  const int icon_sizes[] = {48, 32, 24, 16};

  Fl_PNG_Image *icons[4];
  int count;

  count = 0;

  // FIXME: Follow icon theme specification
  for (size_t i = 0;i < sizeof(icon_sizes)/sizeof(icon_sizes[0]);i++) {
      char icon_path[PATH_MAX];
      bool exists;

      sprintf(icon_path, "%s/icons/hicolor/%dx%d/apps/tightgateviewer.png",
              CMAKE_INSTALL_FULL_DATADIR, icon_sizes[i], icon_sizes[i]);

#ifndef WIN32
      struct stat st;
      if (stat(icon_path, &st) != 0)
#else
      struct _stat st;
      if (_stat(icon_path, &st) != 0)
          return(false);
#endif
        exists = false;
      else
        exists = true;

      if (exists) {
          vlog.debug("Found an icon");
          icons[count] = new Fl_PNG_Image(icon_path);
          if (icons[count]->w() == 0 ||
              icons[count]->h() == 0 ||
              icons[count]->d() != 4) {
              delete icons[count];
              continue;
          }

          count++;
      }
  }

  Fl_Window::default_icons((const Fl_RGB_Image**)icons, count);

  for (int i = 0;i < count;i++)
      delete icons[i];
#endif

  // This makes the "icon" in dialogs rounded, which fits better
  // with the above schemes.
  fl_message_icon()->box(FL_UP_BOX);

  // Turn off the annoying behaviour where popups track the mouse.
  fl_message_hotspot(false);

  // Avoid empty titles for popups
  char default_title[256];
  snprintf(default_title, 256, _("%s Viewer"), productName.getData());
  fl_message_title_default(default_title);

  // Proper Gnome Shell integration requires that we set a sensible
  // WM_CLASS for the window.
  Fl_Window::default_xclass("tightgateviewer");

#ifdef WIN32
  // Most "normal" Windows apps use this font for UI elements.
  Fl::set_font(FL_HELVETICA, "Tahoma");
#endif

  // FLTK exposes these so that we can translate them.
  fl_no     = _("No");
  fl_yes    = _("Yes");
  fl_ok     = _("OK");
  fl_cancel = _("Cancel");
  fl_close  = _("Close");

#ifdef __APPLE__
  /* Needs trailing space */
  static char fltk_about[16];
  snprintf(fltk_about, sizeof(fltk_about), "%s ", _("About"));
  Fl_Mac_App_Menu::about = fltk_about;
  static char fltk_hide[16];
  snprintf(fltk_hide, sizeof(fltk_hide), "%s ", _("Hide"));
  Fl_Mac_App_Menu::hide = fltk_hide;
  static char fltk_quit[16];
  snprintf(fltk_quit, sizeof(fltk_quit), "%s ", _("End"));
  Fl_Mac_App_Menu::quit = fltk_quit;

  Fl_Mac_App_Menu::print = ""; // Don't want the print item
  Fl_Mac_App_Menu::services = _("Services");
  Fl_Mac_App_Menu::hide_others = _("Hide Others");
  Fl_Mac_App_Menu::show = _("Show All");

  fl_mac_set_about(about_callback, NULL);

  Fl::visual(FL_RGB);

  Fl_Sys_Menu_Bar *menubar;
  char buffer[1024];
  menubar = new Fl_Sys_Menu_Bar(0, 0, 500, 25);
  // Fl_Sys_Menu_Bar overrides methods without them being virtual,
  // which means we cannot use our generic Fl_Menu_ helpers.
  if (fltk_menu_escape(p_("SysMenu|", "&File"),
                       buffer, sizeof(buffer)) < sizeof(buffer))
      menubar->add(buffer, 0, 0, 0, FL_SUBMENU);
  if (fltk_menu_escape(p_("SysMenu|File|", "&New Connection"),
                       buffer, sizeof(buffer)) < sizeof(buffer))
      menubar->insert(1, buffer, FL_COMMAND | 'n', new_connection_cb);
#endif
  // Enable fltk multi-threading support
  int fltkMultiThreadingSupport = Fl::lock();
  vlog.info("fltkMultiThreadingSupport: %d", fltkMultiThreadingSupport);
}

static void mkvnchomedir()
{
  // Create .vnc in the user's home directory if it doesn't already exist
  char* homeDir = NULL;

  if (getvnchomedir(&homeDir) == -1) {
    vlog.error("Could not create VNC home directory: can't obtain home "
                 "directory path.");
  } else {
    int result = mkdir(homeDir, 0755);
    if (result == -1 && errno != EEXIST) {
      vlog.error("Could not create VNC home directory %s: %s.", homeDir, strerror(errno));
      error_check(errno, L"Der Konfigurationsordner konnte leider nicht erstellt werden.");
    }
    delete [] homeDir;
  }
}

static void usage(const char *programName)
{
#ifdef WIN32
  // If we don't have a console then we need to create one for output
  if (GetConsoleWindow() == NULL) {
    HANDLE handle;
    int fd;

    AllocConsole();

    handle = GetStdHandle(STD_ERROR_HANDLE);
    fd = _open_osfhandle((intptr_t)handle, O_TEXT);
    *stderr = *fdopen(fd, "w");
  }
#endif

  fprintf(stderr,
          "\n"
          "usage: %s [parameters] [host][:displayNum]\n"
          "       %s [parameters] [host][::port]\n"
#ifndef WIN32
          "       %s [parameters] [unix socket]\n"
#endif
          "       %s [parameters] -listen [port]\n"
          "       %s [parameters] [.tigervnc file]\n",
          programName, programName,
#ifndef WIN32
          programName,
#endif
          programName, programName);

#if !defined(WIN32) && !defined(__APPLE__)
  fprintf(stderr,"\n"
          "Options:\n\n"
          "  -display Xdisplay  - Specifies the X display for the viewer window\n"
          "  -geometry geometry - Initial position of the main VNC viewer window. See the\n"
          "                       man page for details.\n");
#endif

  fprintf(stderr,"\n"
          "Parameters can be turned on with -<param> or off with -<param>=0\n"
          "Parameters which take a value can be specified as "
          "-<param> <value>\n"
          "Other valid forms are <param>=<value> -<param>=<value> "
          "--<param>=<value>\n"
          "Parameter names are case-insensitive.  The parameters are:\n\n");
  Configuration::listParams(79, 14);

#ifdef WIN32
  // Just wait for the user to kill the console window
  Sleep(INFINITE);
#endif

  exit(1);
}

static void
potentiallyLoadConfigurationFile(char *vncServerName)
{
  const bool hasPathSeparator = (strchr(vncServerName, '/') != NULL ||
                                 (strchr(vncServerName, '\\')) != NULL);

  if (hasPathSeparator) {
#ifndef WIN32
    struct stat sb;

    // This might be a UNIX socket, we need to check
    if (stat(vncServerName, &sb) == -1) {
      // Some access problem; let loadViewerParameters() deal with it...
    } else {
      if ((sb.st_mode & S_IFMT) == S_IFSOCK)
        return;
    }
#endif

    try {
      const char* newServerName;
      newServerName = loadViewerParameters(vncServerName);
      // This might be empty, but we still need to clear it so we
      // don't try to connect to the filename
      strncpy(vncServerName, newServerName, VNCSERVERNAMELEN-1);
      vncServerName[VNCSERVERNAMELEN-1] = '\0';
    } catch (rfb::Exception& e) {
      vlog.error("%s", e.str());
      exit_vncviewer(_("Error reading configuration file \"%s\":\n\n%s"),
                     vncServerName, e.str());
    }
  }
}

#ifndef WIN32
static int
interpretViaParam(char *remoteHost, int *remotePort, int localPort)
{
  const int SERVER_PORT_OFFSET = 5900;
  char *pos = strchr(vncServerName, ':');
  if (pos == NULL)
    *remotePort = SERVER_PORT_OFFSET;
  else {
    int portOffset = SERVER_PORT_OFFSET;
    size_t len;
    *pos++ = '\0';
    len = strlen(pos);
    if (*pos == ':') {
      /* Two colons is an absolute port number, not an offset. */
      pos++;
      len--;
      portOffset = 0;
    }
    if (!len || strspn (pos, "-0123456789") != len )
      return 1;
    *remotePort = atoi(pos) + portOffset;
  }

  if (*vncServerName != '\0')
    strncpy(remoteHost, vncServerName, VNCSERVERNAMELEN - 1);
  else
    strncpy(remoteHost, "localhost", VNCSERVERNAMELEN - 1);

  remoteHost[VNCSERVERNAMELEN - 1] = '\0';

  snprintf(vncServerName, VNCSERVERNAMELEN, "localhost::%d", localPort);
  vncServerName[VNCSERVERNAMELEN - 1] = '\0';

  vlog.error("%s", vncServerName);

  return 0;
}

static void
createTunnel(const char *gatewayHost, const char *remoteHost,
             int remotePort, int localPort)
{
  const char *cmd = getenv("VNC_VIA_CMD");
  char *cmd2, *percent;
  char lport[10], rport[10];
  sprintf(lport, "%d", localPort);
  sprintf(rport, "%d", remotePort);
  setenv("G", gatewayHost, 1);
  setenv("H", remoteHost, 1);
  setenv("R", rport, 1);
  setenv("L", lport, 1);
  if (!cmd)
    cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20";
  /* Compatibility with TigerVNC's method. */
  cmd2 = strdup(cmd);
  while ((percent = strchr(cmd2, '%')) != NULL)
    *percent = '$';
  system(cmd2);
  free(cmd2);
}

static int mktunnel()
{
  const char *gatewayHost;
  char remoteHost[VNCSERVERNAMELEN];
  int localPort = findFreeTcpPort();
  int remotePort;

  if (interpretViaParam(remoteHost, &remotePort, localPort) != 0)
    return 1;
  gatewayHost = (const char*)via;
  createTunnel(gatewayHost, remoteHost, remotePort, localPort);

  return 0;
}
#endif /* !WIN32 */

void text_window(const char* windowtitle, const char* windowtext) {
	Fl_Window *win = new Fl_Window(800, 600, windowtitle);
	Fl_Text_Buffer *buff = new Fl_Text_Buffer();
	Fl_Text_Display *disp = new Fl_Text_Display(20, 20, 800-40, 600-40);
	disp->buffer(buff);
	win->show();
	buff->text(windowtext);
	Fl::run();
}

void examine_cli_parameters(int argc, char** argv) {
	int i = 1;
	if (argc < 1)
		return;

	for (; i < argc; i++) {
		if (Configuration::setParam(argv[i])) {
			continue;
		}

		if (argv[i][0] == '-') {
			if (i+1 < argc) {
				if (Configuration::setParam(&argv[i][1], argv[i+1])) {
					i++;
					continue;
				}
			}
			if (argv[i][1] == 'h' && argv[i][2] == '\0') {
				char outbuf[8096];
				Configuration::global()->Configuration::listToString(120, 20, outbuf, 8096);
				text_window("Liste der TightGate Viewer-Parameter", outbuf);
				exit(0);
			}
			fl_message("Mir wurde der Startparameter „%s“ übergeben, den ich leider nicht "
				   "verstehe.\nDeswegen breche in die Ausführung dieses Viewer-Programms "
				   "nun ab. Bitte korrigieren Sie den Programmaufruf.", argv[i]);
			usage(argv[0]);
		}

		strncpy(vncServerName, argv[i], VNCSERVERNAMELEN);
		vncServerName[VNCSERVERNAMELEN - 1] = '\0';
	}
}

#ifdef WIN32
bool sessionLocked = false;
int sys_msg_handler(void* event, void* data) {
	MSG* winmsg = (MSG*)event;
	if (winmsg->message == WM_WTSSESSION_CHANGE) {
		if (winmsg->wParam == WTS_SESSION_LOCK)
			sessionLocked = true;
		else if (winmsg->wParam == WTS_SESSION_UNLOCK)
			sessionLocked = false;
	}
	return 0;
}
#endif

#if defined(WIN32) || defined(WIN64)
void send_url(CConn* cc, char* url) {
	if (cc->writer()) {
		if (!cc->writer()->sendURL(url)) { // Multi is on
			vlog.debug("Could not send using multi. Is multi on? Trying with clipboard instead.\n");
			char mp_url[4096];

			snprintf(mp_url, 4095, "mpurl:%s", url);
			mp_url[4095] = 0;
			try {
				cc->writer()->writeClientCutText(mp_url);
			} catch (rdr::Exception& e) {
				vlog.error("Fatal error occurred while trying to send a url through the clipboard: %s", e.str());
				exit_vncviewer("%s", e.str());
			}
		}
	}
}

void write_pipe_name_to_file(char* pipe_name) {
	char* magicurl_pipe_name_file_path = get_tmp_magicurl_pipe_name_file_path();

	vlog.debug("Saving pipe name ('%s') to %s", pipe_name, magicurl_pipe_name_file_path);

	if (file_exists(magicurl_pipe_name_file_path)) {
		vlog.debug("File exists. Overwriting.");
	}

	FILE* magicurl_pipe_name_file;
	if (!(magicurl_pipe_name_file = fopen(magicurl_pipe_name_file_path, "w"))) {
		vlog.debug("Failed to open %s.", magicurl_pipe_name_file_path);
		return;
	}

	fputs(pipe_name, magicurl_pipe_name_file);
	fclose(magicurl_pipe_name_file);
}

void restore_viewer_window() {
	HWND hwndMain = FindWindow("tightgateviewer", NULL); // tightgateviewer is the WindowClass set with Fl_Window::default_xclass
	if (hwndMain) {
		vlog.debug("%s: found window  with WindowClass 'tightgateviewer'. Trying to restore...", __func__);
		ShowWindow(hwndMain, SW_RESTORE);
	} else {
		vlog.error("%s: Failed to FindWindow with ClassName tightgateviewer. Can't restore.", __func__);
	}
}

DWORD WINAPI magicurl_pipe_thread(void* cc) {
	vlog.info("magicurl_pipe_thread(): Creating a pipe to listen for urls from magicurl");
	char* pipe_name = MAGICURL_PIPE;
	HANDLE pipe_handle = CreateNamedPipe(TEXT(pipe_name), PIPE_ACCESS_DUPLEX | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_WAIT, 1, 1024 * 16, 1024 * 16, NMPWAIT_USE_DEFAULT_WAIT, NULL);

	// If it does not create to open the normal pipe, try to open one with a random string (still open the standard one for backwards compatibility)
	if (pipe_handle == INVALID_HANDLE_VALUE) {
		int count = 0;
		while (pipe_handle == INVALID_HANDLE_VALUE && count < 100) {
			char* random_string = randomstring(10);
			if (random_string) {
				char random_pipe_name[strlen(MAGICURL_PIPE) + 11];
				strcpy(random_pipe_name, MAGICURL_PIPE);
				strcat(random_pipe_name, random_string);
				pipe_name = random_pipe_name;
				vlog.debug("Trying to open a pipe with this name: %s", pipe_name);
				pipe_handle = CreateNamedPipe(TEXT(pipe_name), PIPE_ACCESS_DUPLEX | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_WAIT, 1, 1024 * 16, 1024 * 16, NMPWAIT_USE_DEFAULT_WAIT, NULL);
				free(random_string);
			}
			count++;
		}
	}

	if (pipe_handle != INVALID_HANDLE_VALUE) {
		write_pipe_name_to_file(pipe_name);
	}

	while (pipe_handle != INVALID_HANDLE_VALUE) {
		vlog.debug("Created pipe to listen for urls successfully: %s", pipe_name);
		if (ConnectNamedPipe(pipe_handle, NULL) != FALSE) {  // wait for someone to connect to the pipe
			char buffer[1024];
			DWORD dwRead;
			while (ReadFile(pipe_handle, buffer, sizeof(buffer) - 1, &dwRead, NULL) != FALSE) {
				buffer[dwRead] = '\0';
				vlog.info("Read data from pipe: %s", buffer);
				send_url((CConn*) cc, buffer);
				restore_viewer_window();
			}
		}
		DisconnectNamedPipe(pipe_handle);
		vlog.debug("Finished reading from pipe. Re-opening it...");
	}

	vlog.error("Error: failed to create pipe.");
	return 0;
}
#endif /* WIN32 || WIN64 */

static int userWantsToStartANewSession = 1;

static void showNewSessionPromptCallback(Fl_Widget* widget, void* val) {
  userWantsToStartANewSession = (fl_intptr_t) val;
  widget->window()->hide();
}

int showNewSessionPrompt()
{
  vlog.info("%s: newSessionPrompt is set to 1. Asking the user whether they want to start a new session", __func__);
  const char* new_session_prompt_title = _("Warning");
  const char* new_session_prompt_text = _("Warning! A TightGate Viewer is already running. Do you wish to start a new one?");

  int win_width = scale(580);
  int win_height = scale(145);

  Fl_Window* win = new NormalEnterDialog(win_width, win_height, new_session_prompt_title);
#ifdef WIN32
  POINT cursorPos;
  if(GetCursorPos(&cursorPos))
    win->position(cursorPos.x, cursorPos.y);
#else
    win->position(Fl::w() / 2 - win->w() / 2,
      Fl::h() / 2 - win->h() / 2);
#endif

  Fl_Box* box = new Fl_Box(scale(30), scale(10), scale(560), scale(40), new_session_prompt_text);
  box->align(FL_ALIGN_INSIDE | FL_ALIGN_WRAP | FL_ALIGN_LEFT);

  Fl_Button* noButton = new Fl_Button(win->w() - scale(100), scale(110), scale(90), scale(25), fl_no);
  noButton->align(FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
  noButton->callback(showNewSessionPromptCallback, (void*) 0);
  noButton->shortcut(FL_Escape);

  Fl_Return_Button* yesButton = new Fl_Return_Button(win->w() - scale(200), scale(110), scale(90), scale(25), fl_yes);
  yesButton->align(FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
  yesButton->callback(showNewSessionPromptCallback, (void*) 1);

  scaleWidgetFonts(win);

  win->end();
  win->show();
  return Fl::run(); // If I don't run this, the rest of the program continues to load, and although this might be nice, the program is not yet ready to handle the feedback asynchronously
}

void showNewSessionPromptIfNecessary()
{
  if (!newSessionPrompt) { // If set to false, start a new session directly, without asking (like it used to be)
    vlog.info("%s: newSessionPrompt is set to 0. Starting a new session directly (like it used to be)", __func__);
    return;
  }

  const char* current_user = get_user_name();
#ifdef WIN32
  DWORD process_id = get_process_id_for_user("vncviewer.exe", current_user);
#else
  int process_id = get_process_id_for_user("tightgateviewer", current_user);
#endif
  if (process_id) { // a vncviewer (or tightgateviewer) process is already running for the current user
    vlog.debug("Viewer is running for user '%s'", current_user);
    showNewSessionPrompt(); // showNewSessionPrompt sets usersWantsToStartANewSeession
  } else {
    vlog.debug("Viewer is not running for user '%s'. Starting a new one...", current_user);
  }
  if (!userWantsToStartANewSession) {
    exit_vncviewer();
  }
}

#ifdef WIN32
char* get_magicurl_pipe_name() {
  char* tmp_magicurl = get_tmp_magicurl_pipe_name_file_path();
  char* pipe_name = NULL;
  vlog.debug("%s: looking for file: %s", __func__, tmp_magicurl);

  FILE* tmp_magicurl_file;
  if ((tmp_magicurl_file = fopen(tmp_magicurl, "r"))) {
    vlog.debug("%s: found magic url temp file (I should be able to read a pipe name from it)", __func__);
    char text_line[4096];
    while (fgets(text_line, 4095, tmp_magicurl_file)) {
      pipe_name = strdup(text_line);
      vlog.debug("%s: read pipe name from file: '%s'", __func__, pipe_name);
      break; // I just want to read the first line, which has the filename
    }
    fclose(tmp_magicurl_file);
  } else {
    vlog.info("%s: couldn't find file that contains the pipe name. Using standard name.", __func__);
  }

  if (!pipe_name) {
    pipe_name = MAGICURL_PIPE;
  }
  vlog.debug("%s: pipe name: %s", __func__, pipe_name);
  return pipe_name;
}
#endif /* WIN32 */

#ifdef WIN32
int send_url_to_vncviewer_with_pipe(const char* url) {
  vlog.debug("%s: Trying to send a WindowsMessage to a possibly running vnvciewer", __func__);
  char* url_encoded = url_semi_encode(url);
  if (!url_encoded) {
    vlog.error("%s: failed to encode url. Can't send url through pipe", __func__);
    return 0;
  }
  vlog.debug("%s: url_encoded: %s", __func__, url_encoded);
  char* magicurl_pipe_name = get_magicurl_pipe_name();
  vlog.debug("%s: magicurl_pipe_name: %s", __func__, magicurl_pipe_name);
  HANDLE hPipe;
  DWORD dw_written;
  int succeeded = 0;
  hPipe = CreateFile(TEXT(magicurl_pipe_name), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  if (hPipe != INVALID_HANDLE_VALUE) {
    succeeded = WriteFile(hPipe, url_encoded, strlen(url_encoded) + 1, &dw_written, NULL) ? 1 : 0;
    if (succeeded) {
      vlog.debug("Sent (encoded) URL: '%s' through pipe '%s'", url_encoded, magicurl_pipe_name);
    }
    CloseHandle(hPipe);
  }
  free(url_encoded);
  return succeeded;
}
#endif /* WIN32 */

void fixInitialUrl()
{
  char* initialUrlTemp = initialURL.getData();
  if (initialUrlTemp) {
    char* tmp = initialUrlTemp;
    if (starts_with(initialUrlTemp, "tightgate://") && strlen(initialUrlTemp) > strlen("tightgate://")) {
      vlog.debug("Detected 'tightgate' protocol: %s. Removing 'tightgate://'...", initialUrlTemp);
      tmp += strlen("tightgate://");
      initialURL.setParam(tmp);
    }
    if (starts_with(tmp, "http//")) {
      char* newUrl = (char*) malloc(strlen(tmp) + 2);
      if (newUrl) {
        strcpy(newUrl, "http://");
        strcat(newUrl, tmp + 6);
        initialURL.setParam(newUrl);
        free(newUrl);
      }
    } else if (starts_with(tmp, "https//")) {
      char* newUrl = (char*) malloc(strlen(tmp) + 2);
      if (newUrl) {
        strcpy(newUrl, "https://");
        strcat(newUrl, tmp + 7);
        initialURL.setParam(newUrl);
        free(newUrl);
      }
    }
    free(initialUrlTemp);
  }
}

int main(int argc, char** argv)
{
  const char *localedir;
  UserDialog dlg;

  argv0 = argv[0];

  setlocale(LC_ALL, "");

  localedir = getlocaledir();
  if (localedir == NULL)
    fprintf(stderr, "Failed to determine locale directory\n");
  else
    bindtextdomain(PACKAGE_NAME, localedir);

  textdomain(PACKAGE_NAME);

  rfb::SecurityClient::setDefaults();

  // Write about text to console, still using normal locale codeset
  fprintf(stderr,"\n%s\n", about_text());

  // Set gettext codeset to what our GUI toolkit uses. Since we are
  // passing strings from strerror/gai_strerror to the GUI, these must
  // be in GUI codeset as well.
  bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
  bind_textdomain_codeset("libc", "UTF-8");

  rfb::initStdIOLoggers();
#ifdef WIN32
  const char* defaultViewerLogFilePath = get_viewer_log_file_path();
  if (defaultViewerLogFilePath) {
    // This just replaces '.log' with '-YYYYMMDDHHmm-$pid.log'
    char* defaultViewerLogFilePathWithoutExtension = strdup(defaultViewerLogFilePath);
    char* dotLog = strstr(defaultViewerLogFilePathWithoutExtension, ".log");
    if (dotLog) {
      dotLog[0] = '\0';
    }

    time_t rawtime;
    struct tm timeinfo;
    char timestamp[16];

    time(&rawtime);
    localtime_s(&timeinfo, &rawtime);
    strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M", &timeinfo);

    size_t sessionLogFilePathSize = strlen(defaultViewerLogFilePathWithoutExtension) + 13 + 14 + 4 + 1; // +13 for "-YYYYMMDDHHmm", +14 for "-$pid", +4 for ".log", +1 for '\0'
    char* sessionLogFilePath = (char*) malloc(sessionLogFilePathSize);

    snprintf(sessionLogFilePath, sessionLogFilePathSize, "%s-%s-%lu.log",
             defaultViewerLogFilePathWithoutExtension, timestamp, GetCurrentProcessId());
    sessionLogFilePath[sessionLogFilePathSize - 1] = '\0';

    // free(defaultViewerLogFilePathWithoutExtension);

    rfb::initFileLogger(sessionLogFilePath); // In the user's %temp%/tgprotemp dir

    free(sessionLogFilePath);  // Free allocated memory for sessionLogFilePath
  } else {
    rfb::initFileLogger("C:\\temp\\vncviewer.log");
  }

#else
  rfb::initFileLogger(logFile);
  rfb::initSyslogLogger();
#endif
  rfb::LogWriter::setLogParams("*:stderr:30");

#ifdef SIGHUP
  signal(SIGHUP, CleanupSignalHandler);
#endif
  signal(SIGINT, CleanupSignalHandler);
  signal(SIGTERM, CleanupSignalHandler);

  signal(SIGSEGV, SegvSignalHandler);
#if !defined(WIN32) && !defined(WIN64)
  signal(SIGUSR1, Usr1SignalHandler);
  signal(SIGUSR2, Usr2SignalHandler);
#endif

  enableDpiAwareness();
  g_scale = computeDpiScale(); // set global variable g_scale in dpi_scale.h

  init_fltk();

  Configuration::enableViewerParams();

  // We have to do this check before we start examining cli params, as the browsers call it with params like 'chrome-extension://$id', which the viewer doesn't understand
  if (NativeMessagingApp::startedAsNativeMessagingApp(argc, argv)) {
    vlog.info("Started VNC viewer as native messaging app (probably being called from an add-on)");
    NativeMessagingApp::readLoop();
    vlog.debug("Finished communicating with add-on. Exiting...");
    exit(0);
  }

  /* Read command line params first time to get a config file name. */
  examine_cli_parameters(argc, argv);

  vlog.info("%s %s version %s", vendorName.getData(), productName.getData(), PACKAGE_VERSION);
#if defined(WIN32)
  if (IsWindows10OrGreater())
    vlog.info("Running on Windows 10 or greater");
  else if (IsWindows8Point1OrGreater())
    vlog.info("Running on Windows 8.1 or greater");
  else if (IsWindows8OrGreater())
    vlog.info("Running on Windows 8 or greater");
  else if (IsWindows7SP1OrGreater())
    vlog.info("Running on Windows 7 SP1 or greater");
  else if (IsWindows7OrGreater())
    vlog.info("Running on Windows 7 or greater");
  else
    vlog.info("Running on unknown Windows version");
#else
  struct utsname myUtsname;
  if (!uname(&myUtsname))
    vlog.info("Running on %s system \"%s\" with kernel %s", myUtsname.sysname, myUtsname.nodename, myUtsname.release);
#endif

  if (localedir)
    vlog.debug("localedir is %s, textdomain is %s", localedir, PACKAGE_NAME);
  else
    vlog.info("localedir not set!");

#ifdef WIN32
  int vncviewerCount = count_processes_for_user("vncviewer.exe", get_user_name());
  if (vncviewerCount == 0) {
    vncviewerCount = 1;
  }  vlog.debug("%s: %d vncviewer.exe processes running for user %s", __func__, vncviewerCount, get_user_name());
#endif /* WIN32 */
  vlog.debug("%s: Using scale %.2f", __func__, g_scale);

  vlog.info("Starting setDefaults()");
  rfb::SecurityClient::setDefaults();

  /* Now read params from config file (if there is one).  */
  const char* defaultServerName = NULL;
  if (noConfigFile) {
    SecurityClient::secTypes.setParam("TLSPLainMulti,TLSPlain,X509Plain");
  } else {
    if (strlen(configFile) > 0)
      try {
        defaultServerName = loadViewerParameters(configFile);
      } catch (rfb::Exception& e) {
        vlog.error("%s", e.str());
        dlg.showMsgBox(UserMsgBox::M_OK, NULL, e.str());
        return 1;
      }
    else if (strlen(rwConfigFile) > 0)
      try {
        defaultServerName = loadViewerParameters(rwConfigFile);
      } catch (rfb::Exception& e) {
        vlog.info("There is a problem reading rwconfig-file %s: %s", rwConfigFile.getValueStr(), e.str());
        defaultServerName = loadViewerParameters(NULL);
      }
    else
      defaultServerName = loadViewerParameters(NULL);
  }

#if 0
  /* We need to resolve an ambiguity for booleans */
  for (int i = 1; i < argc;) {
    if (argv[i][0] == '-' && i+1 < argc) {
      VoidParameter *param;

      param = Configuration::getParam(&argv[i][1]);
      if ((param != NULL) &&
          (dynamic_cast<BoolParameter*>(param) != NULL)) {
        if ((strcasecmp(argv[i+1], "0") == 0) ||
            (strcasecmp(argv[i+1], "1") == 0) ||
            (strcasecmp(argv[i+1], "true") == 0) ||
            (strcasecmp(argv[i+1], "false") == 0) ||
            (strcasecmp(argv[i+1], "yes") == 0) ||
            (strcasecmp(argv[i+1], "no") == 0)) {
            param->setParam(argv[i+1]);
            i += 2;
            continue;
        }
      }
    }

    if (Configuration::setParam(argv[i])) {
      i++;
      continue;
    }

    if (argv[i][0] == '-') {
      if (i+1 < argc) {
        if (Configuration::setParam(&argv[i][1], argv[i+1])) {
          i += 2;
          continue;
        }
      }

      usage(argv[0]);
    }
  }
#endif

  if (tgproCC) {
    char default_title[256];

    productName.setParam("TightGate-Pro (CC) 2.0");
    snprintf(default_title, 256, _("%s Viewer"), productName.getData());
    fl_message_title_default(default_title);

    soundSupport.setParam(false);
    autotransferSupport.setParam(false);
    acceptClipboard.setParam(false);
    sendClipboard.setParam(false);
#if defined(WIN32) || defined(WIN64)
#ifdef HAVE_SETENV
    unsetenv("SSLKEYLOGFILE");
#else
    int len = strlen("SSLKEYLOGFILE") + 2;
    char * str = (char *)malloc(len);
    sprintf(str, "%s=", "SSLKEYLOGFILE");
    _putenv(str);
#endif
#else
    unsetenv("SSLKEYLOGFILE");
#endif
  }

  vlog.info("After reading config file or default configuration defaultServerName is now '%s'", defaultServerName);
  /* Read cli params a second time to overwrite conf file
   * settings, because cli settings are more important. */
  examine_cli_parameters(argc, argv);

  NativeMessagingApp::registerAsNativeMessagingHostApp(configDir.getData());
  fixInitialUrl(); // Remove tightgate:// from url if it came from an add-on

  MagicUrlHelper* magicUrlHelper = new MagicUrlHelper();

  const char* myUrl = initialURL.getData();
  if (myUrl && myUrl[0]) { // This happens before we open a named pipe to start listening for URL's
    vlog.info("%s: trying to send initialURL (%s) through named pipe (if we're veeeery lucky, it'll arrive somewhere)", __func__, myUrl);
    if (magicUrlHelper->sendUrlToViewer(myUrl)) {
      vlog.info("%s: successfully sent url through pipe. Closing current viewer process...", __func__);
#ifdef WIN32
      bring_viewer_window_to_front();
#endif /* WIN32 */
      exit_vncviewer();
    } else {
      vlog.info("%s: failed to send URL through pipe. Continue loading viewer normally...", __func__);
    }
  }


  showNewSessionPromptIfNecessary();

  if (webcamEnabled) {
    vlog.info("Disabling Webcam, please do not try to enable it by parameter!");
    webcamEnabled.setParam(false);
  }
  if (micSupport) {
    vlog.info("Disabling microphone support, please do not try to enable it by parameter!");
    micSupport.setParam(false);
  }

  // Generate the about string now that we get the proper translation
  snprintf(aboutText, sizeof(aboutText), _aboutText,
    vendorName.getData(), productName.getData(),
    (int)sizeof(size_t)*8, PACKAGE_VERSION,
    BUILD_TIMESTAMP);

  if (!::autoSelect.hasBeenSet()) {
    // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
    if (::preferredEncoding.hasBeenSet() || ::fullColour.hasBeenSet() ||
        ::fullColourAlias.hasBeenSet()) {
        ::autoSelect.setParam(false);
    }
  }
  if (!::fullColour.hasBeenSet() && !::fullColourAlias.hasBeenSet()) {
    // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
    if (!::autoSelect && (::lowColourLevel.hasBeenSet() ||
        ::lowColourLevelAlias.hasBeenSet())) {
      ::fullColour.setParam(false);
    }
  }
  if (!::customCompressLevel.hasBeenSet()) {
    // Default to CustomCompressLevel=1 if CompressLevel is used.
    if(::compressLevel.hasBeenSet()) {
      ::customCompressLevel.setParam(true);
    }
  }

  /* Always set fullColour */
  ::fullColour.setParam(true);

#if !defined(WIN32) && !defined(__APPLE__)
  if (strcmp(display, "") != 0) {
    Fl::display(display);
  }
  fl_open_display();
  XkbSetDetectableAutoRepeat(fl_display, True, NULL);
#endif

  init_fltk();
  enable_touch();

  // Check if the server name in reality is a configuration file
  potentiallyLoadConfigurationFile(vncServerName);

  mkvnchomedir();

  CSecurity::upg = &dlg;
#ifdef HAVE_GNUTLS
  CSecurityTLS::msg = &dlg;
#endif

  Socket *sock = NULL;

#ifndef WIN32
  /* Specifying -via and -listen together is nonsense */
  if (listenMode && strlen(via.getValueStr()) > 0) {
    // TRANSLATORS: "Parameters" are command line arguments, or settings
    // from a file or the Windows registry.
    vlog.error("Parameters -listen and -via are incompatible");
    exit_vncviewer(_("Parameters -listen and -via are incompatible"));
    return 1; /* Not reached */
  }
#endif

  if (magicUrlHelper->maybeSetStandardBrowser()) {
    vlog.debug("The user chose to open the Windows web browser choice dialog. The Viewer needs to be restarted once it is manually set as standard. Exiting Viewer...");
    exit_vncviewer();
  }

  vlog.debug("Done magicUrlHelper");

#ifdef WIN32
  // Explicitely delete shell:sendto/TightGate-Pro.lnk if it exists (to remove old remnants of the old way of doing this)
  delete_send_to_shortcut();
  //  NativeMessagingApp::registerAsNativeMessagingHostApp(configDir.getData());
#endif /* #ifdef WIN32 */

  if (listenMode) {
    std::list<SocketListener*> listeners;
    try {
      int port = 5500;
      if (isdigit(vncServerName[0]))
        port = atoi(vncServerName);

#if !defined(__APPLE__)
      createTcpListeners(&listeners, 0, port);
#endif

      vlog.info("Listening on port %d", port);

      /* Wait for a connection */
      while (sock == NULL) {
        fd_set rfds;
        FD_ZERO(&rfds);
        for (std::list<SocketListener*>::iterator i = listeners.begin();
             i != listeners.end();
             i++)
          FD_SET((*i)->getFd(), &rfds);

        int n = select(FD_SETSIZE, &rfds, 0, 0, 0);
        if (n < 0) {
          if (errno == EINTR) {
            vlog.debug("Interrupted select() system call");
            continue;
          } else {
            throw rdr::SystemException("select", errno);
          }
        }

        for (std::list<SocketListener*>::iterator i = listeners.begin ();
             i != listeners.end();
             i++)
          if (FD_ISSET((*i)->getFd(), &rfds)) {
            sock = (*i)->accept();
            if (sock)
              /* Got a connection */
              break;
          }
      }
    } catch (rdr::Exception& e) {
      vlog.error("%s", e.str());
      exit_vncviewer(_("Failure waiting for incoming VNC connection:\n\n%s"), e.str());
      return 1; /* Not reached */
    }

    while (!listeners.empty()) {
      delete listeners.back();
      listeners.pop_back();
    }
  } else {
    if (vncServerName[0] == '\0') {
      if (defaultServerName && strlen(defaultServerName)) {
        strncpy(vncServerName, defaultServerName, VNCSERVERNAMELEN);
        vncServerName[VNCSERVERNAMELEN - 1] = '\0';
      } else {
        ServerDialog::run(defaultServerName, vncServerName);
        if (vncServerName[0] == '\0')
          return 1;
      }
    }

#ifndef WIN32
    if (strlen (via.getValueStr()) > 0 && mktunnel() != 0)
      usage(argv[0]);
#endif
  }

  size_t wintit_len = 64 + strlen(vendorName.getData() + strlen(productName.getData()));
  char * connecting_window_title;
  connecting_window_title = (char *) malloc(wintit_len);
  if (connecting_window_title) {
    snprintf(connecting_window_title, wintit_len - 1, _("%s %s Viewer Connection"), vendorName.getData(), productName.getData());
    wait_window = new ConnectingMessage(connecting_window_title, vncServerName, productName.getData());
    wait_window->show();
  }

  CConn *cc = new CConn(vncServerName, sock);

  inMainloop = true;
  if (cc->state() == rfb::CConnection::RFBSTATE_INVALID) {
    delete wait_window;
    wait_window = NULL;
  }
  char* peer_address = strdup(cc->getPeerAddress());
  sound::set_conn(cc);

  /* Create a pipe to listen for urls from magicurl */
#if defined(WIN32) || defined(WIN64)
  if (!CreateThread(NULL, 0, magicurl_pipe_thread, cc, 0, NULL)) {
    vlog.error("Failed to create thread for pipe (to listen for urls to open)");
  }
#endif

#ifdef WIN32
  Fl::add_system_handler(sys_msg_handler, NULL);
#endif
  Fl::wait(1);

  vlog.debug("Entering main loop");

  while (!exitMainloop)
    run_mainloop(cc);
  inMainloop = false;

  if (wait_window) {
    delete wait_window;
    wait_window = NULL;
  }

  sound::stop_sound();
  cc->security.stopMultiThreads();

  if (exitError != NULL && alertOnFatalError)
  {
    /* Filter out "Connection reset by peer" errors on Linux, OS X, and Woe32 / Woe64. */
    /* Filter out "writeTLS: error in the push function errors" on Woe32. */
    if (strstr(exitError, "(104)") ||
        strstr(exitError, "(10054)") ||
        strstr(exitError, "(54)") ||
        strstr(exitError, "(-10)") ||
        strstr(exitError, "(-53)")) {
      vlog.debug("There is an error, but I don't tell the user about it: %s", exitError);
      exitError = NULL;
    } else if(strstr(exitError, "invalid password or username")) {
      char * buf = (char *) malloc(1024);
      if (buf) {
        snprintf(buf, 1023, _("The username or password was incorrect, or the maximum number of users has been reached.\nThe connection to %s has failed."), peer_address);
        exitError = buf;
      }
    } else if(strstr(exitError, "hostname cert mismatch")) {
      exitError = _("Aborted due to the X509 certificate not matching the server.\nPlease notify your system administrator.");
    } else if(strstr(exitError, "Could not setup user environment")) {
      char * buf = (char *) malloc(2048);
      if (buf) {
        snprintf(buf, 2047, _("The centralized access control does not permit you to log in. Please contact your system administrator.\n\n%s"), exitError);
        exitError = buf;
      }
    } else if(strstr(exitError, "(10053)")) {
      exitError = _("The connection has been terminated");
    } else if(strstr(exitError, "No matching security types")) {
      exitError = _("No suitable security types found. Please contact your system administration.");
    }
  }
  if (!exitError) {
    if (!noConfigFile) {
      try {
        if (rwConfigFile && strlen(rwConfigFile))
          saveViewerParameters(rwConfigFile, vncServerName);
        else if (!configFile || !strlen(configFile))
          saveViewerParameters(NULL, vncServerName);
        else
          vlog.info("I don't store parameters because there is only a readonly cfg file.");
      } catch (rfb::Exception& e) {
        dlg.showMsgBox(UserMsgBox::M_OK, NULL, e.str());
      }
    }
  } else {
    if (noConfigFile)
      vlog.info("An error occured. But I also do not want to store config in file because of noConfigFile parameter.");
    else
      vlog.info("I do not save config parameters to file, because an error occured. I don't want to store broken configuration.");
    dlg.showMsgBox(UserMsgBox::M_OK, NULL, exitError);
  }
  free(peer_address);

  delete cc;

  vlog.info("Exiting.");

  return 0;
}
