/**
 * Remmina - The GTK+ Remote Desktop Client - SSH plugin.
 *
 * @copyright Copyright (C) 2010-2011 Vic Lee.
 * @copyright Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo.
 * @copyright Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo.
 * @copyright Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka
 *
 * This program 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 program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU General Public License in all respects
 *  for all of the code used other than OpenSSL. *  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so. *  If you
 *  do not wish to do so, delete this exception statement from your
 *  version. *  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include "config.h"
#include "remmina/remmina_trace_calls.h"

#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <glib-object.h>
#include <gobject/gvaluecollector.h>
#include <vte/vte.h>
#include <locale.h>
#include <langinfo.h>
#include "remmina_log.h"
#include "remmina_public.h"
#include "remmina_plugin_manager.h"
#include "remmina_ssh.h"
#include "remmina_protocol_widget.h"
#include "remmina_pref.h"
#include "remmina_ssh_plugin.h"
#include "remmina_masterthread_exec.h"

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>


#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY  1
#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE 2
#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL 3
#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT 4
#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT 5
#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH 6

#define GET_PLUGIN_DATA(gp) (RemminaPluginSshData *)g_object_get_data(G_OBJECT(gp), "plugin-data");

/** Palette colors taken from sakura */
#define PALETTE_SIZE 16
/* Min fontsize and increase */
#define FONT_SCALE    0.75
#define SCALE_FACTOR  0.1
#define FONT_MINIMAL_SIZE (PANGO_SCALE * 6)

enum color_schemes { LINUX, TANGO, GRUVBOX, SOLARIZED_DARK, SOLARIZED_LIGHT, XTERM, CUSTOM };

/** 16 colour palettes in GdkRGBA format (red, green, blue, alpha).
 * Text displayed in the first 8 colours (0-7) is meek (uses thin strokes).
 * Text displayed in the second 8 colours (8-15) is bold (uses thick strokes).
 **/
const GdkRGBA linux_palette[PALETTE_SIZE] = {
	{ 0,	    0,	      0,	1 },
	{ 0.666667, 0,	      0,	1 },
	{ 0,	    0.666667, 0,	1 },
	{ 0.666667, 0.333333, 0,	1 },
	{ 0,	    0,	      0.666667, 1 },
	{ 0.666667, 0,	      0.666667, 1 },
	{ 0,	    0.666667, 0.666667, 1 },
	{ 0.666667, 0.666667, 0.666667, 1 },
	{ 0.333333, 0.333333, 0.333333, 1 },
	{ 1,	    0.333333, 0.333333, 1 },
	{ 0.333333, 1,	      0.333333, 1 },
	{ 1,	    1,	      0.333333, 1 },
	{ 0.333333, 0.333333, 1,	1 },
	{ 1,	    0.333333, 1,	1 },
	{ 0.333333, 1,	      1,	1 },
	{ 1,	    1,	      1,	1 }
};

const GdkRGBA tango_palette[PALETTE_SIZE] = {
	{ 0,	     0,	       0,	 1 },
	{ 0.8,	     0,	       0,	 1 },
	{ 0.305882,  0.603922, 0.023529, 1 },
	{ 0.768627,  0.627451, 0,	 1 },
	{ 0.203922,  0.396078, 0.643137, 1 },
	{ 0.458824,  0.313725, 0.482353, 1 },
	{ 0.0235294, 0.596078, 0.603922, 1 },
	{ 0.827451,  0.843137, 0.811765, 1 },
	{ 0.333333,  0.341176, 0.32549,	 1 },
	{ 0.937255,  0.160784, 0.160784, 1 },
	{ 0.541176,  0.886275, 0.203922, 1 },
	{ 0.988235,  0.913725, 0.309804, 1 },
	{ 0.447059,  0.623529, 0.811765, 1 },
	{ 0.678431,  0.498039, 0.658824, 1 },
	{ 0.203922,  0.886275, 0.886275, 1 },
	{ 0.933333,  0.933333, 0.92549,	 1 }
};

const GdkRGBA gruvbox_palette[PALETTE_SIZE] = {
	{ 0.156863, 0.156863, 0.156863, 1.000000 },
	{ 0.800000, 0.141176, 0.113725, 1.000000 },
	{ 0.596078, 0.592157, 0.101961, 1.000000 },
	{ 0.843137, 0.600000, 0.129412, 1.000000 },
	{ 0.270588, 0.521569, 0.533333, 1.000000 },
	{ 0.694118, 0.384314, 0.525490, 1.000000 },
	{ 0.407843, 0.615686, 0.415686, 1.000000 },
	{ 0.658824, 0.600000, 0.517647, 1.000000 },
	{ 0.572549, 0.513725, 0.454902, 1.000000 },
	{ 0.984314, 0.286275, 0.203922, 1.000000 },
	{ 0.721569, 0.733333, 0.149020, 1.000000 },
	{ 0.980392, 0.741176, 0.184314, 1.000000 },
	{ 0.513725, 0.647059, 0.596078, 1.000000 },
	{ 0.827451, 0.525490, 0.607843, 1.000000 },
	{ 0.556863, 0.752941, 0.486275, 1.000000 },
	{ 0.921569, 0.858824, 0.698039, 1.000000 },
};

const GdkRGBA solarized_dark_palette[PALETTE_SIZE] = {
	{ 0.027451, 0.211765, 0.258824, 1 },
	{ 0.862745, 0.196078, 0.184314, 1 },
	{ 0.521569, 0.600000, 0.000000, 1 },
	{ 0.709804, 0.537255, 0.000000, 1 },
	{ 0.149020, 0.545098, 0.823529, 1 },
	{ 0.827451, 0.211765, 0.509804, 1 },
	{ 0.164706, 0.631373, 0.596078, 1 },
	{ 0.933333, 0.909804, 0.835294, 1 },
	{ 0.000000, 0.168627, 0.211765, 1 },
	{ 0.796078, 0.294118, 0.086275, 1 },
	{ 0.345098, 0.431373, 0.458824, 1 },
	{ 0.396078, 0.482353, 0.513725, 1 },
	{ 0.513725, 0.580392, 0.588235, 1 },
	{ 0.423529, 0.443137, 0.768627, 1 },
	{ 0.576471, 0.631373, 0.631373, 1 },
	{ 0.992157, 0.964706, 0.890196, 1 }
};

const GdkRGBA solarized_light_palette[PALETTE_SIZE] = {
	{ 0.933333, 0.909804, 0.835294, 1 },
	{ 0.862745, 0.196078, 0.184314, 1 },
	{ 0.521569, 0.600000, 0.000000, 1 },
	{ 0.709804, 0.537255, 0.000000, 1 },
	{ 0.149020, 0.545098, 0.823529, 1 },
	{ 0.827451, 0.211765, 0.509804, 1 },
	{ 0.164706, 0.631373, 0.596078, 1 },
	{ 0.027451, 0.211765, 0.258824, 1 },
	{ 0.992157, 0.964706, 0.890196, 1 },
	{ 0.796078, 0.294118, 0.086275, 1 },
	{ 0.576471, 0.631373, 0.631373, 1 },
	{ 0.513725, 0.580392, 0.588235, 1 },
	{ 0.396078, 0.482353, 0.513725, 1 },
	{ 0.423529, 0.443137, 0.768627, 1 },
	{ 0.345098, 0.431373, 0.458824, 1 },
	{ 0.000000, 0.168627, 0.211765, 1 }
};

const GdkRGBA xterm_palette[PALETTE_SIZE] = {
	{ 0,	    0,	      0,	1 },
	{ 0.803922, 0,	      0,	1 },
	{ 0,	    0.803922, 0,	1 },
	{ 0.803922, 0.803922, 0,	1 },
	{ 0.117647, 0.564706, 1,	1 },
	{ 0.803922, 0,	      0.803922, 1 },
	{ 0,	    0.803922, 0.803922, 1 },
	{ 0.898039, 0.898039, 0.898039, 1 },
	{ 0.298039, 0.298039, 0.298039, 1 },
	{ 1,	    0,	      0,	1 },
	{ 0,	    1,	      0,	1 },
	{ 1,	    1,	      0,	1 },
	{ 0.27451,  0.509804, 0.705882, 1 },
	{ 1,	    0,	      1,	1 },
	{ 0,	    1,	      1,	1 },
	{ 1,	    1,	      1,	1 }
};

#define DEFAULT_PALETTE "linux_palette"


/** The SSH plugin implementation */
typedef struct _RemminaSshSearch {
	GtkWidget *		parent;

	GtkBuilder *		builder;
	GtkWidget *		window;
	GtkWidget *		search_entry;
	GtkWidget *		search_prev_button;
	GtkWidget *		search_next_button;
	GtkWidget *		close_button;
	GtkToggleButton *	match_case_checkbutton;
	GtkToggleButton *	entire_word_checkbutton;
	GtkToggleButton *	regex_checkbutton;
	GtkToggleButton *	wrap_around_checkbutton;
	GtkWidget *		reveal_button;
	GtkWidget *		revealer;

	//VteTerminal *terminal;
	gboolean		regex_caseless;
	gboolean		has_regex;
	gchar *			regex_pattern;
} RemminaSshSearch;

typedef struct _RemminaPluginSshData {
	RemminaSSHShell *	shell;
	GFile *			vte_session_file;
	GtkWidget *		vte;

	const GdkRGBA *		palette;

	pthread_t		thread;

	gboolean		closed;

	RemminaSshSearch *	search_widget;
} RemminaPluginSshData;


#define GET_OBJECT(object_name) gtk_builder_get_object(search_widget->builder, object_name)

static RemminaPluginService *remmina_plugin_service = NULL;

static gboolean
remmina_plugin_ssh_on_size_allocate(GtkWidget *widget, GtkAllocation *alloc, RemminaProtocolWidget *gp);

static gboolean
valid_color(GdkRGBA const *color)
{
	return color->red >= 0. && color->red <= 1. &&
	       color->green >= 0. && color->green <= 1. &&
	       color->blue >= 0. && color->blue <= 1. &&
	       color->alpha >= 0. && color->alpha <= 1.;
}


/**
 * Remmina protocol plugin main function.
 *
 * First it starts the SSH tunnel if needed and then the SSH connection.
 *
 */
static gpointer
remmina_plugin_ssh_main_thread(gpointer data)
{
	TRACE_CALL(__func__);
	RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
	RemminaPluginSshData *gpdata;
	RemminaFile *remminafile;
	RemminaSSH *ssh;
	RemminaSSHShell *shell = NULL;
	gboolean cont = FALSE;
	gboolean partial = FALSE;
	gchar *hostport;
	gint ret = REMMINA_SSH_AUTH_NULL;

	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
	CANCEL_ASYNC

		gpdata = GET_PLUGIN_DATA(gp);

	remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);

	/* we may need to open a new tunnel */
	REMMINA_DEBUG("Tentatively create an SSH tunnel");
	hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
	if (hostport == NULL) {
		remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
		return NULL;
	}
	REMMINA_DEBUG("protocol_plugin_start_direct_tunnel returned hostport: %s", hostport);

	ssh = g_object_get_data(G_OBJECT(gp), "user-data");
	if (ssh) {
		REMMINA_DEBUG("Creating SSH shell based on existing SSH session");
		shell = remmina_ssh_shell_new_from_ssh(ssh);
		REMMINA_DEBUG("Calling remmina_public_get_server_port");
		remmina_plugin_service->get_server_port(hostport, 22, &ssh->tunnel_entrance_host, &ssh->tunnel_entrance_port);
		REMMINA_DEBUG("tunnel_entrance_host: %s, tunnel_entrance_port: %d", ssh->tunnel_entrance_host, ssh->tunnel_entrance_port);

		if (remmina_ssh_init_session(REMMINA_SSH(shell)) &&
		    remmina_ssh_auth(REMMINA_SSH(shell), NULL, gp, remminafile) == REMMINA_SSH_AUTH_SUCCESS &&
		    remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
					   remmina_plugin_service->protocol_plugin_signal_connection_closed, gp))
			cont = TRUE;
	} else {
		/* New SSH Shell connection */
		REMMINA_DEBUG("Creating SSH shell based on a new SSH session");
		shell = remmina_ssh_shell_new_from_file(remminafile);
		ssh = REMMINA_SSH(shell);
		REMMINA_DEBUG("Calling remmina_public_get_server_port");
		remmina_plugin_service->get_server_port(hostport, 22, &ssh->tunnel_entrance_host, &ssh->tunnel_entrance_port);
		REMMINA_DEBUG("tunnel_entrance_host: %s, tunnel_entrance_port: %d", ssh->tunnel_entrance_host, ssh->tunnel_entrance_port);

		while (1) {
			if (!partial) {
				if (!remmina_ssh_init_session(ssh)) {
					REMMINA_DEBUG("init session error: %s", ssh->error);
					remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
					// exit the loop here: OK
					break;
				}
			}

			ret = remmina_ssh_auth_gui(ssh, gp, remminafile);
			switch (ret) {
			case REMMINA_SSH_AUTH_SUCCESS:
				REMMINA_DEBUG("Authentication success");
				if (!remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
							    remmina_plugin_service->protocol_plugin_signal_connection_closed, gp)) {
					remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
					break;
				}
				gchar *server;
				gint port;
				remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
						22,
						&server,
						&port);

				REMMINA_AUDIT(_("Connected to %s:%d via SSH"), server, port);
				g_free(server), server = NULL;
				break;
			case REMMINA_SSH_AUTH_PARTIAL:
				REMMINA_DEBUG("Continue with the next auth method");
				partial = TRUE;
				// Continue the loop: OK
				continue;
				break;
			case REMMINA_SSH_AUTH_RECONNECT:
				REMMINA_DEBUG("Reconnecting…");
				if (ssh->session) {
					ssh_disconnect(ssh->session);
					ssh_free(ssh->session);
					ssh->session = NULL;
				}
				g_free(ssh->callback);
				// Continue the loop: OK
				continue;
				break;
			case REMMINA_SSH_AUTH_USERCANCEL:
				REMMINA_DEBUG("Interrupted by the user");
				// TODO: exit the loop here: OK
				goto BREAK;
				break;
			default:
				REMMINA_DEBUG("Error during the authentication: %s", ssh->error);
				remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
				// TODO: exit the loop here: OK
				goto BREAK;
			}


			cont = TRUE;
			break;
		}
	}

BREAK:
	REMMINA_DEBUG("Authentication terminated with exit status %d", ret);

	g_free(hostport);

	if (!cont) {
		if (shell) remmina_ssh_shell_free(shell);
		remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
		return NULL;
	}

	gpdata->shell = shell;

	gchar *charset = REMMINA_SSH(shell)->charset;

	remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VTE_TERMINAL(gpdata->vte), charset, shell->master, shell->slave);

	/* TODO: The following call should be moved on the main thread, or something weird could happen */
	//remmina_plugin_ssh_on_size_allocate(GTK_WIDGET(gpdata->vte), NULL, gp);

	remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);

	gpdata->thread = 0;
	return NULL;
}

void remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VteTerminal *terminal, const char *codeset, int master, int slave)
{
	TRACE_CALL(__func__);
	if (!remmina_masterthread_exec_is_main_thread()) {
		/* Allow the execution of this function from a non main thread */
		RemminaMTExecData *d;
		d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
		d->func = FUNC_VTE_TERMINAL_SET_ENCODING_AND_PTY;
		d->p.vte_terminal_set_encoding_and_pty.terminal = terminal;
		d->p.vte_terminal_set_encoding_and_pty.codeset = codeset;
		d->p.vte_terminal_set_encoding_and_pty.master = master;
		remmina_masterthread_exec_and_wait(d);
		g_free(d);
		return;
	}

	setlocale(LC_ALL, "");
	if (codeset && codeset[0] != '\0') {
	}

	vte_terminal_set_backspace_binding(terminal, VTE_ERASE_ASCII_DELETE);
	vte_terminal_set_delete_binding(terminal, VTE_ERASE_DELETE_SEQUENCE);

#if VTE_CHECK_VERSION(0, 38, 0)
	/* vte_pty_new_foreign expect master FD, see https://bugzilla.gnome.org/show_bug.cgi?id=765382 */
	vte_terminal_set_pty(terminal, vte_pty_new_foreign_sync(master, NULL, NULL));
#else
	vte_terminal_set_pty(terminal, master);
#endif
}

static gboolean
remmina_plugin_ssh_on_focus_in(GtkWidget *widget, GdkEventFocus *event, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	gtk_widget_grab_focus(gpdata->vte);
	return TRUE;
}

static gboolean
remmina_plugin_ssh_on_size_allocate(GtkWidget *widget, GtkAllocation *alloc, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
	gint cols, rows;

	if (!gtk_widget_get_mapped(widget)) return FALSE;

	cols = vte_terminal_get_column_count(VTE_TERMINAL(widget));
	rows = vte_terminal_get_row_count(VTE_TERMINAL(widget));

	if (gpdata->shell)
		remmina_ssh_shell_set_size(gpdata->shell, cols, rows);

	return FALSE;
}

static void
remmina_plugin_ssh_set_vte_pref(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
	RemminaFile *remminafile;

	remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);

	if (remmina_plugin_service->file_get_int(remminafile, "audiblebell", FALSE)) {
		vte_terminal_set_audible_bell(VTE_TERMINAL(gpdata->vte), TRUE);
		g_info("audible_bell set to %i", vte_terminal_get_audible_bell(VTE_TERMINAL(gpdata->vte)));
	}
	if (remmina_pref.vte_font && remmina_pref.vte_font[0]) {
#if !VTE_CHECK_VERSION(0, 38, 0)
		vte_terminal_set_font_from_string(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_font);
#else
		vte_terminal_set_font(VTE_TERMINAL(gpdata->vte),
				      pango_font_description_from_string(remmina_pref.vte_font));
#endif
	}

#if VTE_CHECK_VERSION(0, 51, 3)
	REMMINA_DEBUG("Using vte_terminal_set_bold_is_bright instead of vte_terminal_set_allow_bold as deprecated");
	vte_terminal_set_bold_is_bright(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_allow_bold_text);
#else
	vte_terminal_set_allow_bold(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_allow_bold_text);
#endif
	if (remmina_pref.vte_lines > 0)
		vte_terminal_set_scrollback_lines(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_lines);
}

void
remmina_plugin_ssh_vte_select_all(GtkMenuItem *menuitem, gpointer vte)
{
	TRACE_CALL(__func__);
	vte_terminal_select_all(VTE_TERMINAL(vte));
	/** @todo we should add the vte_terminal_unselect_all as well */
}

void
remmina_plugin_ssh_vte_decrease_font(GtkMenuItem *menuitem, gpointer vte)
{
	TRACE_CALL(__func__);
	vte_terminal_set_font_scale(VTE_TERMINAL(vte), vte_terminal_get_font_scale(VTE_TERMINAL(vte)) - SCALE_FACTOR);
}

void
remmina_plugin_ssh_vte_increase_font(GtkMenuItem *menuitem, gpointer vte)
{
	TRACE_CALL(__func__);
	vte_terminal_set_font_scale(VTE_TERMINAL(vte), vte_terminal_get_font_scale(VTE_TERMINAL(vte)) + SCALE_FACTOR);
}

void
remmina_plugin_ssh_vte_copy_clipboard(GtkMenuItem *menuitem, gpointer vte)
{
	TRACE_CALL(__func__);
#if VTE_CHECK_VERSION(0, 50, 0)
	vte_terminal_copy_clipboard_format(VTE_TERMINAL(vte), VTE_FORMAT_TEXT);
#else
	vte_terminal_copy_clipboard(VTE_TERMINAL(vte));
#endif
}

void
remmina_plugin_ssh_vte_paste_clipboard(GtkMenuItem *menuitem, gpointer vte)
{
	TRACE_CALL(__func__);
	vte_terminal_paste_clipboard(VTE_TERMINAL(vte));
}

void
remmina_plugin_ssh_vte_save_session(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	GtkWidget *widget;
	GError *err = NULL;

	GFileOutputStream *stream = g_file_replace(gpdata->vte_session_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &err);

	if (err != NULL) {
		// TRANSLATORS: %s is a placeholder for an error message
		widget = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Error: %s"), err->message);
		g_signal_connect(G_OBJECT(widget), "response", G_CALLBACK(gtk_widget_destroy), NULL);
		gtk_widget_show(widget);
		return;
	}

	if (stream != NULL)
#if VTE_CHECK_VERSION(0, 38, 0)
		vte_terminal_write_contents_sync(VTE_TERMINAL(gpdata->vte), G_OUTPUT_STREAM(stream),
						 VTE_WRITE_DEFAULT, NULL, &err);
#else
		vte_terminal_write_contents(VTE_TERMINAL(gpdata->vte), G_OUTPUT_STREAM(stream),
					    VTE_TERMINAL_WRITE_DEFAULT, NULL, &err);
#endif

	if (err == NULL) {
		remmina_public_send_notification("remmina-terminal-saved",
						 _("Terminal content saved in"),
						 g_file_get_path(gpdata->vte_session_file));
	}

	g_object_unref(stream);
	g_free(err);
}

/** Send a keystroke to the plugin window */
static void remmina_ssh_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->vte,
								  keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE);
	return;
}


/* regex */

static void jit_regex(VteRegex *regex, char const *pattern)
{
	TRACE_CALL(__func__);
	GError *error;

	if (!vte_regex_jit(regex, PCRE2_JIT_COMPLETE, &error) ||
	    !vte_regex_jit(regex, PCRE2_JIT_PARTIAL_SOFT, &error))
		if (!g_error_matches(error, VTE_REGEX_ERROR, -45 /* PCRE2_ERROR_JIT_BADOPTION: JIT not supported */))
			REMMINA_DEBUG("JITing regex “%s” failed: %s\n", pattern, error->message);
}

static VteRegex *compile_regex_for_search(char const *pattern, gboolean caseless, GError **error)
{
	TRACE_CALL(__func__);
	uint32_t flags = PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MULTILINE;

	if (caseless)
		flags |= PCRE2_CASELESS;

	VteRegex *regex = vte_regex_new_for_search(pattern, strlen(pattern), flags, error);

	if (regex != NULL)
		jit_regex(regex, pattern);

	return regex;
}

static void
remmina_search_widget_update_sensitivity(RemminaSshSearch *search_widget)
{
	TRACE_CALL(__func__);
	gboolean can_search = search_widget->has_regex;

	gtk_widget_set_sensitive(search_widget->search_next_button, can_search);
	gtk_widget_set_sensitive(search_widget->search_prev_button, can_search);
}

static void
remmina_search_widget_update_regex(RemminaPluginSshData *gpdata)
{
	TRACE_CALL(__func__);
	GError *error = NULL;

	RemminaSshSearch *search_widget = gpdata->search_widget;
	char const *search_text = gtk_entry_get_text(GTK_ENTRY(search_widget->search_entry));
	gboolean caseless = gtk_toggle_button_get_active(search_widget->match_case_checkbutton) == FALSE;

	char *pattern;

	if (gtk_toggle_button_get_active(search_widget->regex_checkbutton))
		pattern = g_strdup(search_text);
	else
		pattern = g_regex_escape_string(search_text, -1);

	if (gtk_toggle_button_get_active(search_widget->regex_checkbutton)) {
		char *tmp = g_strdup_printf("\\b%s\\b", pattern);
		g_free(pattern);
		pattern = tmp;
	}

	if (caseless == search_widget->regex_caseless &&
	    g_strcmp0(pattern, search_widget->regex_pattern) == 0)
		return;

	search_widget->regex_caseless = caseless;
	g_free(search_widget->regex_pattern);
	search_widget->regex_pattern = NULL;

	if (search_text[0] != '\0') {
		REMMINA_DEBUG("Search text is: %s", search_text);
		VteRegex *regex = compile_regex_for_search(pattern, caseless, &error);
		vte_terminal_search_set_regex(VTE_TERMINAL(gpdata->vte), regex, 0);
		if (regex != NULL)
			vte_regex_unref(regex);

		if (!error) {
			search_widget->has_regex = TRUE;
			search_widget->regex_pattern = pattern; /* adopt */
			pattern = NULL;                         /* adopted */
			gtk_widget_set_tooltip_text(search_widget->search_entry, NULL);
		} else {
			search_widget->has_regex = FALSE;
			REMMINA_DEBUG("Regex not set, cannot search");
			gtk_widget_set_tooltip_text(search_widget->search_entry, error->message);
		}
	}

	g_free(pattern);
	g_free(error);

	remmina_search_widget_update_sensitivity(search_widget);
}

static void
remmina_search_widget_wrap_around_toggled(GtkToggleButton *button, RemminaPluginSshData *gpdata)
{
	TRACE_CALL(__func__);

	vte_terminal_search_set_wrap_around(VTE_TERMINAL(gpdata->vte), gtk_toggle_button_get_active(button));
}

static void
remmina_search_widget_search_forward(RemminaPluginSshData *gpdata)
{
	TRACE_CALL(__func__);

	RemminaSshSearch *search_sidget = gpdata->search_widget;

	if (!search_sidget->has_regex)
		return;
	vte_terminal_search_find_next(VTE_TERMINAL(gpdata->vte));
}

static void
remmina_search_widget_search_backward(RemminaPluginSshData *gpdata)
{
	TRACE_CALL(__func__);

	RemminaSshSearch *search_sidget = gpdata->search_widget;

	if (!search_sidget->has_regex)
		return;
	vte_terminal_search_find_previous(VTE_TERMINAL(gpdata->vte));
}

GtkWidget *remmina_plugin_pop_search_new(GtkWidget *relative_to, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	gpdata->search_widget = g_new0(RemminaSshSearch, 1);
	RemminaSshSearch *search_widget = gpdata->search_widget;

	search_widget->regex_caseless = FALSE;
	search_widget->has_regex = FALSE;
	search_widget->regex_pattern = NULL;

	search_widget->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_search.glade");
	search_widget->window = GTK_WIDGET(GET_OBJECT("RemminaSearchWidget"));
	search_widget->search_entry = GTK_WIDGET(GET_OBJECT("search_entry"));
	search_widget->search_prev_button = GTK_WIDGET(GET_OBJECT("search_prev_button"));
	search_widget->search_next_button = GTK_WIDGET(GET_OBJECT("search_next_button"));
	search_widget->close_button = GTK_WIDGET(GET_OBJECT("close_button"));
	search_widget->match_case_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("match_case_checkbutton"));
	search_widget->entire_word_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("entire_word_checkbutton"));
	search_widget->regex_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("regex_checkbutton"));
	search_widget->wrap_around_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("wrap_around_checkbutton"));
	search_widget->reveal_button = GTK_WIDGET(GET_OBJECT("reveal_button"));
	search_widget->revealer = GTK_WIDGET(GET_OBJECT("revealer"));

	gtk_widget_set_can_default(search_widget->search_next_button, TRUE);
	gtk_widget_grab_default(search_widget->search_next_button);

	gtk_entry_set_activates_default(GTK_ENTRY(search_widget->search_entry), TRUE);

	/* Connect signals */
	gtk_builder_connect_signals(search_widget->builder, NULL);

	g_signal_connect_swapped(search_widget->close_button, "clicked", G_CALLBACK(gtk_widget_destroy), GTK_WIDGET(search_widget->window));

	g_object_bind_property(search_widget->reveal_button, "active",
			       search_widget->revealer, "reveal-child",
			       G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);

	g_signal_connect_swapped(search_widget->search_entry, "next-match", G_CALLBACK(remmina_search_widget_search_forward), gpdata);
	g_signal_connect_swapped(search_widget->search_entry, "previous-match", G_CALLBACK(remmina_search_widget_search_backward), gpdata);
	g_signal_connect_swapped(search_widget->search_entry, "search-changed", G_CALLBACK(remmina_search_widget_update_regex), gpdata);

	g_signal_connect_swapped(search_widget->search_next_button, "clicked", G_CALLBACK(remmina_search_widget_search_forward), gpdata);
	g_signal_connect_swapped(search_widget->search_prev_button, "clicked", G_CALLBACK(remmina_search_widget_search_backward), gpdata);

	g_signal_connect_swapped(search_widget->match_case_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
	g_signal_connect_swapped(search_widget->entire_word_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
	g_signal_connect_swapped(search_widget->regex_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
	g_signal_connect_swapped(search_widget->match_case_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);

	g_signal_connect(search_widget->wrap_around_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_wrap_around_toggled), gpdata);

	remmina_search_widget_update_sensitivity(search_widget);
	return search_widget->window;
}

void remmina_plugin_pop_search(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	GtkWindow *parent = NULL;

	REMMINA_DEBUG("Before popover");
	GtkWidget *window = remmina_plugin_pop_search_new(gpdata->vte, gp);
	GtkWidget *toplevel = gtk_widget_get_toplevel(gpdata->vte);

	if (GTK_IS_WINDOW(toplevel)) {
		parent = GTK_WINDOW(toplevel);
		gtk_window_set_transient_for(GTK_WINDOW(window), parent);
		gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
	}
	gtk_widget_show(window);
	REMMINA_DEBUG("After popover");
}

void remmina_plugin_ssh_call_sftp(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	remmina_protocol_widget_call_feature_by_type(gp, REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PROTOCOL_FEATURE_TOOL_SFTP);
}

gboolean
remmina_ssh_plugin_popup_menu(GtkWidget *widget, GdkEvent *event, GtkWidget *menu)
{
	if ((event->type == GDK_BUTTON_PRESS) && (((GdkEventButton *)event)->button == 3)) {
#if GTK_CHECK_VERSION(3, 22, 0)
		gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
#else
		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
			       ((GdkEventButton *)event)->button, gtk_get_current_event_time());
#endif
		return TRUE;
	}

	return FALSE;
}

gboolean
remmina_ssh_plugin_copy_selection(GtkWidget *widget, GdkEvent *event, GtkWidget *menu)
{
	if ((event->type == GDK_BUTTON_RELEASE) && (((GdkEventButton *)event)->button == 1)) {
		if (vte_terminal_get_has_selection(VTE_TERMINAL(widget))){
				vte_terminal_copy_clipboard_format(VTE_TERMINAL(widget), VTE_FORMAT_TEXT);
		}
	}

	return FALSE;
}

/**
 * Remmina SSH plugin terminal popup menu.
 *
 * This is the context menu that popup when you right click in a terminal window.
 * You can than select, copy, paste text and save the whole buffer to a file.
 * Each menu entry call back the following functions:
 * - remmina_plugin_ssh_vte_select_all()
 * - remmina_plugin_ssh_vte_copy_clipboard()
 * - remmina_plugin_ssh_vte_paste_clipboard()
 * - remmina_plugin_ssh_vte_save_session()
 * .
 *
 */
void remmina_plugin_ssh_popup_ui(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
	/* Context menu for slection and clipboard */
	GtkWidget *menu = gtk_menu_new();

	GtkWidget *select_all = gtk_menu_item_new_with_label(_("Select All (host+A)"));
	GtkWidget *copy = gtk_menu_item_new_with_label(_("Copy (host+C)"));
	GtkWidget *paste = gtk_menu_item_new_with_label(_("Paste (host+V)"));
	GtkWidget *save = gtk_menu_item_new_with_label(_("Save session to file"));
	GtkWidget *font_incr = gtk_menu_item_new_with_label(_("Increase font size (host+Page Up)"));
	GtkWidget *font_decr = gtk_menu_item_new_with_label(_("Decrease font size (host+Page Down)"));
	GtkWidget *find_text = gtk_menu_item_new_with_label(_("Find text (host+G)"));
	GtkWidget *sftp = gtk_menu_item_new_with_label(_("Open SFTP transfer…"));

	gtk_menu_shell_append(GTK_MENU_SHELL(menu), select_all);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), paste);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), save);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), font_incr);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), font_decr);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), find_text);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), sftp);

	g_signal_connect(G_OBJECT(gpdata->vte), "button_press_event",
			 G_CALLBACK(remmina_ssh_plugin_popup_menu), menu);

	g_signal_connect(G_OBJECT(select_all), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_select_all), gpdata->vte);
	g_signal_connect(G_OBJECT(copy), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_copy_clipboard), gpdata->vte);
	g_signal_connect(G_OBJECT(paste), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_paste_clipboard), gpdata->vte);
	g_signal_connect(G_OBJECT(save), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_save_session), gp);
	g_signal_connect(G_OBJECT(font_incr), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_increase_font), gpdata->vte);
	g_signal_connect(G_OBJECT(font_decr), "activate",
			 G_CALLBACK(remmina_plugin_ssh_vte_decrease_font), gpdata->vte);
	g_signal_connect(G_OBJECT(find_text), "activate",
			 G_CALLBACK(remmina_plugin_pop_search), gp);
	g_signal_connect(G_OBJECT(sftp), "activate",
			 G_CALLBACK(remmina_plugin_ssh_call_sftp), gp);

	gtk_widget_show_all(menu);
}

static void
remmina_plugin_ssh_eof(VteTerminal *vteterminal, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

	RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	if (gpdata->closed)
		return;

	if (remmina_file_get_int(remminafile, "sshlogenabled", FALSE))
		remmina_plugin_ssh_vte_save_session(NULL, gp);

	gchar *server;
	gint port;
	remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
			22,
			&server,
			&port);

	REMMINA_AUDIT(_("Disconnected from %s:%d via SSH"), server, port);
	g_free(server), server = NULL;
	gpdata->closed = TRUE;
}

static gboolean
remmina_plugin_ssh_close_connection(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	RemminaFile *remminafile;

	REMMINA_DEBUG("Requesting to close the connection");
	remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);

	if (remmina_file_get_int(remminafile, "sshlogenabled", FALSE))
		remmina_plugin_ssh_vte_save_session(NULL, gp);
	remmina_plugin_ssh_eof(VTE_TERMINAL(gpdata->vte), gp);

	if (gpdata->thread) {
		pthread_cancel(gpdata->thread);
		if (gpdata->thread) pthread_join(gpdata->thread, NULL);
	}
	if (gpdata->shell) {
		remmina_ssh_shell_free(gpdata->shell);
		gpdata->shell = NULL;
	}

	remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
	return FALSE;
}

/**
 * Remmina SSH plugin initialization.
 *
 * This is the main function used to create the widget that will be embedded in the
 * Remmina Connection Window.
 * Initialize the terminal colours based on the user, everything is needed for the
 * terminal window, the terminal session logging and the terminal popup menu.
 *
 * @see remmina_plugin_ssh_popup_ui
 * @see RemminaProtocolWidget
 * @see https://gitlab.com/Remmina/Remmina/wikis/Remmina-SSH-Terminal-colour-schemes
 */
static void
remmina_plugin_ssh_init(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata;
	RemminaFile *remminafile;
	GtkWidget *hbox;
	GtkAdjustment *vadjustment;
	GtkWidget *vscrollbar;
	GtkWidget *vte;
	GdkRGBA foreground_color;
	GdkRGBA background_color;
	GdkRGBA palette[PALETTE_SIZE];


#if !VTE_CHECK_VERSION(0, 38, 0)
	GdkColor foreground_gdkcolor;
	GdkColor background_gdkcolor;
#endif  /* VTE_CHECK_VERSION(0,38,0) */

	gpdata = g_new0(RemminaPluginSshData, 1);
	g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);

	gpdata->closed = FALSE;

	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_widget_show(hbox);
	gtk_container_add(GTK_CONTAINER(gp), hbox);
	g_signal_connect(G_OBJECT(hbox), "focus-in-event", G_CALLBACK(remmina_plugin_ssh_on_focus_in), gp);

	vte = vte_terminal_new();
	vte_terminal_set_size(VTE_TERMINAL(vte), 80, 25);
	vte_terminal_set_scroll_on_keystroke(VTE_TERMINAL(vte), TRUE);
#if !VTE_CHECK_VERSION(0, 38, 0)
	gdk_rgba_parse(&foreground_color, remmina_pref.color_pref.foreground);
	gdk_rgba_parse(&background_color, remmina_pref.color_pref.background);
#endif
	remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);


#if VTE_CHECK_VERSION(0, 38, 0)
	GdkRGBA cp[PALETTE_SIZE];
	GdkRGBA cursor_color;
	GdkRGBA cursor_foreground;
	GdkRGBA highlight;
	GdkRGBA highlight_foreground;
	GdkRGBA colorBD;
	unsigned int i = 0;

	/*
	 * custom colors reside inside of the 'theme' subdir of the remmina config folder (.config/remmina/theme)
	 * with the file extension '.colors'. The name of the colorfile came from the menu (see below)
	 * sideeffect: It is possible to overwrite the standard colours with a dedicated colourfile like
	 * '0.colors' for GRUVBOX, '1.colors' for TANGO and so on
	 */
	const gchar *color_name = remmina_plugin_service->file_get_string(remminafile, "ssh_color_scheme");

	gchar *remmina_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "theme", NULL);
	gchar *remmina_colors_file = g_strdup_printf("%s/%s.colors", remmina_dir, color_name);
	g_free(remmina_dir);

	/*
	 * try to load theme from one of the system data dirs (based on XDG_DATA_DIRS environment var)
	 */
	if (!g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
		GError *error = NULL;
		const gchar *const *dirs = g_get_system_data_dirs();

		for (i = 0; dirs[i] != NULL; ++i) {
			remmina_dir = g_build_path("/", dirs[i], "remmina", "theme", NULL);
			GDir *system_data_dir = g_dir_open(remmina_dir, 0, &error);
			// ignoring this error is OK, because the folder may not exist
			if (error) {
				g_error_free(error);
				error = NULL;
			} else {
				if (system_data_dir) {
					g_dir_close(system_data_dir);
					g_free(remmina_colors_file);
					remmina_colors_file = g_strdup_printf("%s/%s.colors", remmina_dir, color_name);
					if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR))
						break;
				}
			}
			g_free(remmina_dir);
		}
	}

	if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
		GKeyFile *gkeyfile;
		RemminaColorPref color_pref;

		gkeyfile = g_key_file_new();
		g_key_file_load_from_file(gkeyfile, remmina_colors_file, G_KEY_FILE_NONE, NULL);
		remmina_pref_file_load_colors(gkeyfile, &color_pref);

		REMMINA_DEBUG("Load custom theme for SSH teminal");
		g_warn_if_fail(gdk_rgba_parse(&foreground_color, color_pref.foreground));
		g_warn_if_fail(gdk_rgba_parse(&background_color, color_pref.background));
		g_warn_if_fail(gdk_rgba_parse(&cursor_color, color_pref.cursor));
		g_warn_if_fail(gdk_rgba_parse(&cursor_foreground, color_pref.cursor_foreground));
		g_warn_if_fail(gdk_rgba_parse(&highlight, color_pref.highlight));
		g_warn_if_fail(gdk_rgba_parse(&highlight_foreground, color_pref.highlight_foreground));
		g_warn_if_fail(gdk_rgba_parse(&colorBD, color_pref.colorBD));

		g_warn_if_fail(gdk_rgba_parse(&cp[0], color_pref.color0));
		g_warn_if_fail(gdk_rgba_parse(&cp[1], color_pref.color1));
		g_warn_if_fail(gdk_rgba_parse(&cp[2], color_pref.color2));
		g_warn_if_fail(gdk_rgba_parse(&cp[3], color_pref.color3));
		g_warn_if_fail(gdk_rgba_parse(&cp[4], color_pref.color4));
		g_warn_if_fail(gdk_rgba_parse(&cp[5], color_pref.color5));
		g_warn_if_fail(gdk_rgba_parse(&cp[6], color_pref.color6));
		g_warn_if_fail(gdk_rgba_parse(&cp[7], color_pref.color7));
		g_warn_if_fail(gdk_rgba_parse(&cp[8], color_pref.color8));
		g_warn_if_fail(gdk_rgba_parse(&cp[9], color_pref.color9));
		g_warn_if_fail(gdk_rgba_parse(&cp[10], color_pref.color10));
		g_warn_if_fail(gdk_rgba_parse(&cp[11], color_pref.color11));
		g_warn_if_fail(gdk_rgba_parse(&cp[12], color_pref.color12));
		g_warn_if_fail(gdk_rgba_parse(&cp[13], color_pref.color13));
		g_warn_if_fail(gdk_rgba_parse(&cp[14], color_pref.color14));
		g_warn_if_fail(gdk_rgba_parse(&cp[15], color_pref.color15));

		const GdkRGBA custom_palette[PALETTE_SIZE] = {
			cp[0],	cp[1],	cp[2],	cp[3],
			cp[4],	cp[5],	cp[6],	cp[7],
			cp[8],	cp[9],	cp[10], cp[11],
			cp[12], cp[13], cp[14], cp[15]
		};

		for (i = 0; i < PALETTE_SIZE; i++)
			palette[i] = custom_palette[i];
	} else {
		/* Set colors to GdkRGBA */
		switch (remmina_plugin_service->file_get_int(remminafile, "ssh_color_scheme", FALSE)) {
		case LINUX:
			gdk_rgba_parse(&foreground_color, "#ffffff");
			gdk_rgba_parse(&background_color, "#000000");
			gdk_rgba_parse(&cursor_color, "#ffffff");
			gdk_rgba_parse(&cursor_foreground, "#00000");
			gdk_rgba_parse(&highlight, "#ffffff");
			gdk_rgba_parse(&highlight_foreground, "#00000");
			gdk_rgba_parse(&colorBD, "#ffffff");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = linux_palette[i];
			break;
		case TANGO:
			gdk_rgba_parse(&foreground_color, "#ffffff");
			gdk_rgba_parse(&background_color, "#000000");
			gdk_rgba_parse(&cursor_color, "#000000");
			gdk_rgba_parse(&cursor_foreground, "#ffffff");
			gdk_rgba_parse(&highlight, "#ffffff");
			gdk_rgba_parse(&highlight_foreground, "#00000");
			gdk_rgba_parse(&colorBD, "#000000");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = tango_palette[i];
			break;
		case GRUVBOX:
			gdk_rgba_parse(&foreground_color, "#e6d4a3");
			gdk_rgba_parse(&background_color, "#1e1e1e");
			gdk_rgba_parse(&cursor_color, "#e6d4a3");
			gdk_rgba_parse(&cursor_foreground, "#e6d4a3");
			gdk_rgba_parse(&highlight, "#e6d4a3");
			gdk_rgba_parse(&highlight_foreground, "#1e1e1e");
			gdk_rgba_parse(&colorBD, "#ffffff");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = gruvbox_palette[i];
			break;
		case SOLARIZED_DARK:
			gdk_rgba_parse(&foreground_color, "#839496");
			gdk_rgba_parse(&background_color, "#002b36");
			gdk_rgba_parse(&cursor_color, "#93a1a1");
			gdk_rgba_parse(&cursor_foreground, "#839496");
			gdk_rgba_parse(&highlight, "#839496");
			gdk_rgba_parse(&highlight_foreground, "#002b36");
			gdk_rgba_parse(&colorBD, "#819090");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = solarized_dark_palette[i];
			break;
		case SOLARIZED_LIGHT:
			gdk_rgba_parse(&foreground_color, "#657b83");
			gdk_rgba_parse(&background_color, "#fdf6e3");
			gdk_rgba_parse(&cursor_color, "#586e75");
			gdk_rgba_parse(&cursor_foreground, "#657b83");
			gdk_rgba_parse(&highlight, "#657b83");
			gdk_rgba_parse(&highlight_foreground, "#fdf6e3");
			gdk_rgba_parse(&colorBD, "#475b62");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = solarized_light_palette[i];
			break;
		case XTERM:
			gdk_rgba_parse(&foreground_color, "#000000");
			gdk_rgba_parse(&background_color, "#ffffff");
			gdk_rgba_parse(&cursor_color, "#000000");
			gdk_rgba_parse(&cursor_foreground, "#ffffff");
			gdk_rgba_parse(&highlight, "#000000");
			gdk_rgba_parse(&highlight_foreground, "#ffffff");
			gdk_rgba_parse(&colorBD, "#000000");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = xterm_palette[i];
			break;
		case CUSTOM:
			REMMINA_DEBUG("Custom colors");
			g_warn_if_fail(gdk_rgba_parse(&foreground_color, remmina_pref.color_pref.foreground));
			g_warn_if_fail(gdk_rgba_parse(&background_color, remmina_pref.color_pref.background));
			g_warn_if_fail(gdk_rgba_parse(&cursor_color, remmina_pref.color_pref.cursor));
			g_warn_if_fail(gdk_rgba_parse(&cursor_foreground, remmina_pref.color_pref.cursor_foreground));
			g_warn_if_fail(gdk_rgba_parse(&highlight, remmina_pref.color_pref.highlight));
			g_warn_if_fail(gdk_rgba_parse(&highlight_foreground, remmina_pref.color_pref.highlight_foreground));
			g_warn_if_fail(gdk_rgba_parse(&colorBD, remmina_pref.color_pref.colorBD));

			g_warn_if_fail(gdk_rgba_parse(&cp[0], remmina_pref.color_pref.color0));
			g_warn_if_fail(gdk_rgba_parse(&cp[1], remmina_pref.color_pref.color1));
			g_warn_if_fail(gdk_rgba_parse(&cp[2], remmina_pref.color_pref.color2));
			g_warn_if_fail(gdk_rgba_parse(&cp[3], remmina_pref.color_pref.color3));
			g_warn_if_fail(gdk_rgba_parse(&cp[4], remmina_pref.color_pref.color4));
			g_warn_if_fail(gdk_rgba_parse(&cp[5], remmina_pref.color_pref.color5));
			g_warn_if_fail(gdk_rgba_parse(&cp[6], remmina_pref.color_pref.color6));
			g_warn_if_fail(gdk_rgba_parse(&cp[7], remmina_pref.color_pref.color7));
			g_warn_if_fail(gdk_rgba_parse(&cp[8], remmina_pref.color_pref.color8));
			g_warn_if_fail(gdk_rgba_parse(&cp[9], remmina_pref.color_pref.color9));
			g_warn_if_fail(gdk_rgba_parse(&cp[10], remmina_pref.color_pref.color10));
			g_warn_if_fail(gdk_rgba_parse(&cp[11], remmina_pref.color_pref.color11));
			g_warn_if_fail(gdk_rgba_parse(&cp[12], remmina_pref.color_pref.color12));
			g_warn_if_fail(gdk_rgba_parse(&cp[13], remmina_pref.color_pref.color13));
			g_warn_if_fail(gdk_rgba_parse(&cp[14], remmina_pref.color_pref.color14));
			g_warn_if_fail(gdk_rgba_parse(&cp[15], remmina_pref.color_pref.color15));

			const GdkRGBA custom_palette[PALETTE_SIZE] = {
				cp[0],          //  remmina_pref.color_pref.color0
				cp[1],          //  remmina_pref.color_pref.color1
				cp[2],          //  remmina_pref.color_pref.color2
				cp[3],          //  remmina_pref.color_pref.color3
				cp[4],          //  remmina_pref.color_pref.color4
				cp[5],          //  remmina_pref.color_pref.color5
				cp[6],          //  remmina_pref.color_pref.color6
				cp[7],          //  remmina_pref.color_pref.color7
				cp[8],          //  remmina_pref.color_pref.color8
				cp[9],          //  remmina_pref.color_pref.color9
				cp[10],         //  remmina_pref.color_pref.color10
				cp[11],         //  remmina_pref.color_pref.color11
				cp[12],         //  remmina_pref.color_pref.color12
				cp[13],         //  remmina_pref.color_pref.color13
				cp[14],         //  remmina_pref.color_pref.color14
				cp[15]          //  remmina_pref.color_pref.color15
			};

			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = custom_palette[i];
			break;
		default:
			REMMINA_DEBUG("Linux paelette colors");
			for (i = 0; i < PALETTE_SIZE; i++)
				palette[i] = linux_palette[i];
			break;
		}
	}
	g_free(remmina_colors_file);
	REMMINA_DEBUG("foreground_color.red %f, background_color.red %f", foreground_color.red, background_color.red);
	REMMINA_DEBUG("foreground_color.blue %f, background_color.blue %f", foreground_color.blue, background_color.blue);
	REMMINA_DEBUG("foreground_color.green %f, background_color.green %f", foreground_color.green, background_color.green);
	REMMINA_DEBUG("foreground_color.alpha %f, background_color.alpha %f", foreground_color.alpha, background_color.alpha);
	for (i = 0; i < PALETTE_SIZE; i++) {
		REMMINA_DEBUG("index: %d, palette validation for red: %f", i, palette[i].red);
		REMMINA_DEBUG("index: %d, palette validation for green: %f", i, palette[i].green);
		REMMINA_DEBUG("index: %d, palette validation for blue: %f", i, palette[i].blue);
		REMMINA_DEBUG("index: %d, palette validation for alpha: %f", i, palette[i].alpha);
		g_warn_if_fail(valid_color(&palette[i]));
	}
	vte_terminal_set_colors(VTE_TERMINAL(vte), &foreground_color, &background_color, palette, PALETTE_SIZE);
	vte_terminal_set_color_foreground(VTE_TERMINAL(vte), &foreground_color);
	vte_terminal_set_color_background(VTE_TERMINAL(vte), &background_color);
	vte_terminal_set_color_cursor(VTE_TERMINAL(vte), &cursor_color);
	vte_terminal_set_color_cursor_foreground(VTE_TERMINAL(vte), &cursor_foreground);
	vte_terminal_set_color_highlight(VTE_TERMINAL(vte), &highlight);
	vte_terminal_set_color_highlight_foreground(VTE_TERMINAL(vte), &highlight_foreground);
	vte_terminal_set_color_bold(VTE_TERMINAL(vte), &colorBD);

#else
	/* VTE <= 2.90 doesn’t support GdkRGBA so we must convert GdkRGBA to GdkColor */
	foreground_gdkcolor.red = (guint16)(foreground_color.red * 0xFFFF);
	foreground_gdkcolor.green = (guint16)(foreground_color.green * 0xFFFF);
	foreground_gdkcolor.blue = (guint16)(foreground_color.blue * 0xFFFF);
	background_gdkcolor.red = (guint16)(background_color.red * 0xFFFF);
	background_gdkcolor.green = (guint16)(background_color.green * 0xFFFF);
	background_gdkcolor.blue = (guint16)(background_color.blue * 0xFFFF);
	/* Set colors to GdkColor */
	vte_terminal_set_colors(VTE_TERMINAL(vte), &foreground_gdkcolor, &background_gdkcolor, NULL, 0);
#endif

	gtk_box_pack_start(GTK_BOX(hbox), vte, TRUE, TRUE, 0);
	gpdata->vte = vte;
	remmina_plugin_ssh_set_vte_pref(gp);

	remmina_plugin_service->protocol_plugin_register_hostkey(gp, vte);

#if VTE_CHECK_VERSION(0, 28, 0)
	vadjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte));
#else
	vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
#endif

	vscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);

	gtk_widget_show(vscrollbar);
	gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, TRUE, 0);

	if (remmina_file_get_int(remminafile, "ssh_copy_on_select", FALSE)){
		g_signal_connect(G_OBJECT(gpdata->vte), "button_release_event",
				G_CALLBACK(remmina_ssh_plugin_copy_selection), gpdata->vte);
	}

	const gchar *dir;
	const gchar *sshlogname;
	gchar *fp;

	GFile *rf = g_file_new_for_path(remminafile->filename);

	if (remmina_plugin_service->file_get_string(remminafile, "sshlogfolder") == NULL)
		dir = g_build_path("/", g_get_user_cache_dir(), "remmina", NULL);
	else
		dir = remmina_plugin_service->file_get_string(remminafile, "sshlogfolder");

	if (remmina_plugin_service->file_get_string(remminafile, "sshlogname") == NULL)
		sshlogname = g_strconcat(g_file_get_basename(rf), ".", "log", NULL);
	else
		sshlogname = remmina_plugin_service->file_get_string(remminafile, "sshlogname");

	sshlogname = remmina_file_format_properties(remminafile, sshlogname);

	fp = g_strconcat(dir, "/", sshlogname, NULL);
	g_free((gpointer) sshlogname);

	gpdata->vte_session_file = g_file_new_for_path(fp);
	g_free(fp);

	g_signal_connect(G_OBJECT(vte), "size-allocate", G_CALLBACK(remmina_plugin_ssh_on_size_allocate), gp);
	g_signal_connect(G_OBJECT(vte), "unrealize", G_CALLBACK(remmina_plugin_ssh_eof), gp);
	g_signal_connect(G_OBJECT(vte), "eof", G_CALLBACK(remmina_plugin_ssh_eof), gp);
	g_signal_connect(G_OBJECT(vte), "child-exited", G_CALLBACK(remmina_plugin_ssh_eof), gp);
	remmina_plugin_ssh_popup_ui(gp);
	gtk_widget_show_all(hbox);
}

/**
 * Initialize the main window properties and the pthread.
 *
 * The call of this function is a requirement of remmina_protocol_widget_open_connection_real().
 * @return TRUE
 * @return FALSE and remmina_protocol_widget_open_connection_real() will fails.
 */
static gboolean
remmina_plugin_ssh_open_connection(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	remmina_plugin_service->protocol_plugin_set_expand(gp, TRUE);
	remmina_plugin_service->protocol_plugin_set_width(gp, 640);
	remmina_plugin_service->protocol_plugin_set_height(gp, 480);

	if (pthread_create(&gpdata->thread, NULL, remmina_plugin_ssh_main_thread, gp)) {
		remmina_plugin_service->protocol_plugin_set_error(gp,
								  "Failed to initialize pthread. Falling back to non-thread mode…");
		gpdata->thread = 0;
		return FALSE;
	} else {
		return TRUE;
	}
	return TRUE;
}

/**
 * Not used by the plugin.
 *
 * @return Always TRUE
 */
static gboolean
remmina_plugin_ssh_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
	TRACE_CALL(__func__);
	return TRUE;
}

/**
 * Functions to call when an entry in the Tool menu in the Remmina Connection Window is clicked.
 *
 * In the Remmina Connection Window toolbar, there is a tool menu, this function is used to
 * call the right function for each entry with its parameters.
 *
 * At the moment it’s possible to:
 * - Open a new SSH session.
 * - Open an SFTP session.
 * - Select, copy and paste text.
 * - Send recorded Key Strokes.
 * .
 *
 * @return the return value of the calling function.
 *
 */
static void
remmina_plugin_ssh_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
	TRACE_CALL(__func__);
	RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);

	switch (feature->id) {
	case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
		remmina_plugin_service->open_connection(
			remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SSH"),
			NULL, gpdata->shell, NULL);
		return;
	case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
		remmina_plugin_service->open_connection(
			/** @todo start the direct tunnel here */
			remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SFTP"),
			NULL, gpdata->shell, NULL);
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY:
#if VTE_CHECK_VERSION(0, 50, 0)
		vte_terminal_copy_clipboard_format(VTE_TERMINAL(gpdata->vte), VTE_FORMAT_TEXT);
#else
		vte_terminal_copy_clipboard(VTE_TERMINAL(gpdata->vte));
#endif
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE:
		vte_terminal_paste_clipboard(VTE_TERMINAL(gpdata->vte));
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL:
		vte_terminal_select_all(VTE_TERMINAL(gpdata->vte));
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT:
		vte_terminal_set_font_scale(VTE_TERMINAL(gpdata->vte),
					    vte_terminal_get_font_scale(VTE_TERMINAL(gpdata->vte)) + SCALE_FACTOR);
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT:
		vte_terminal_set_font_scale(VTE_TERMINAL(gpdata->vte),
					    vte_terminal_get_font_scale(VTE_TERMINAL(gpdata->vte)) - SCALE_FACTOR);
		return;
	case REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH:
		remmina_plugin_pop_search(NULL, gp);
		return;
	}
}

/** Array of key/value pairs for SSH auth type
 * libssh methods:
 *
 * #define SSH_AUTH_METHOD_UNKNOWN     0x0000u
 * #define SSH_AUTH_METHOD_NONE        0x0001u
 * #define SSH_AUTH_METHOD_PASSWORD    0x0002u
 * #define SSH_AUTH_METHOD_PUBLICKEY   0x0004u
 * #define SSH_AUTH_METHOD_HOSTBASED   0x0008u
 * #define SSH_AUTH_METHOD_INTERACTIVE 0x0010u
 * #define SSH_AUTH_METHOD_GSSAPI_MIC  0x0020u
 */
static gpointer ssh_auth[] =
{
	"0", N_("Password"),
	"1", N_("SSH identity file"),
	"2", N_("SSH agent"),
	"3", N_("Public key (automatic)"),
	"4", N_("Kerberos (GSSAPI)"),
	NULL
};

/** Charset list */
static gpointer ssh_charset_list[] =
{
	"", "",
	"", "ASCII",
	"", "BIG5",
	"", "CP437",
	"", "CP720",
	"", "CP737",
	"", "CP775",
	"", "CP850",
	"", "CP852",
	"", "CP855",
	"", "CP857",
	"", "CP858",
	"", "CP862",
	"", "CP866",
	"", "CP874",
	"", "CP1125",
	"", "CP1250",
	"", "CP1251",
	"", "CP1252",
	"", "CP1253",
	"", "CP1254",
	"", "CP1255",
	"", "CP1256",
	"", "CP1257",
	"", "CP1258",
	"", "EUC-JP",
	"", "EUC-KR",
	"", "GBK",
	"", "ISO 8859-1",
	"", "ISO 8859-2",
	"", "ISO 8859-3",
	"", "ISO 8859-4",
	"", "ISO 8859-5",
	"", "ISO 8859-6",
	"", "ISO 8859-7",
	"", "ISO 8859-8",
	"", "ISO 8859-9",
	"", "ISO 8859-10",
	"", "ISO 8859-11",
	"", "ISO 8859-12",
	"", "ISO 8859-13",
	"", "ISO 8859-14",
	"", "ISO 8859-15",
	"", "ISO 8859-16",
	"", "KOI8-R",
	"", "SJIS",
	"", "UTF-8",
	NULL
};

static gpointer ssh_terminal_palette[] =
{
	"0",  "Linux",
	"1",  "Tango",
	"2",  "Gruvbox",
	"3",  "Solarized Dark",
	"4",  "Solarized Light",
	"5",  "XTerm",
	"6",  "Custom (Configured in Remmina preferences)",
	NULL, NULL
};

/**
 * Array for available features.
 * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END.
 */
static RemminaProtocolFeature remmina_plugin_ssh_features[] =
{
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY,	     N_("Copy"),		N_("_Copy"),		   NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE,	     N_("Paste"),		N_("_Paste"),		   NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL,    N_("Select all"),		N_("_Select all"),	   NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT, N_("Increase font size"),	N_("_Increase font size"), NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT, N_("Decrease font size"),	N_("_Decrease font size"), NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH,	     N_("Find text"),		N_("_Find text"),	   NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PROTOCOL_FEATURE_TOOL_SFTP,	     N_("Open SFTP transfer…"), "folder-remote",	   NULL },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_END,  0,					     NULL,			NULL,			   NULL }
};

/* Array of RemminaProtocolSetting for basic settings.
 * Each item is composed by:
 * a) RemminaProtocolSettingType for setting type
 * b) Setting name
 * c) Setting description
 * d) Compact disposition
 * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
 * f) Setting tooltip
 * g) Validation data pointer, will be passed to the validation callback method.
 * h) Validation callback method (Can be NULL. Every entry will be valid then.)
 *		use following prototype:
 *		gboolean mysetting_validator_method(gpointer key, gpointer value,
 *						    gpointer validator_data);
 *		gpointer key is a gchar* containing the setting's name,
 *		gpointer value contains the value which should be validated,
 *		gpointer validator_data contains your passed data.
 */
static const RemminaProtocolSetting remmina_ssh_basic_settings[] =
{
	{ REMMINA_PROTOCOL_SETTING_TYPE_SERVER,	  "server",	    NULL,				  FALSE, "_ssh._tcp", NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_SELECT,	  "ssh_auth",	    N_("Authentication type"),		  FALSE, ssh_auth,    NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	  "username",	    N_("Username"),			  FALSE, NULL,	      NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password",	    N_("User password"),		  FALSE, NULL,	      NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_FILE,	  "ssh_privatekey", N_("SSH identity file"),		  FALSE, NULL,	      NULL, NULL, NULL },
#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)
	{ REMMINA_PROTOCOL_SETTING_TYPE_FILE,	  "ssh_certfile",   N_("SSH certificate file"),		  FALSE, NULL,	      NULL, NULL, NULL },
#endif
	{ REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_passphrase", N_("Password to unlock private key"), FALSE, NULL,	      NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	  "run_line",	    N_("Opening command"),		  FALSE, NULL,	      NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	  "exec",	    N_("Start-up background program"),		  FALSE, NULL,	      NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_END,	  NULL,		    NULL,				  FALSE, NULL,	      NULL, NULL, NULL }
};

static gchar log_tips[] =
	N_("The filename can use the following placeholders:\n\n"
	   "  • %h is substituted with the server name\n"
	   "  • %t is substituted with the SSH server name\n"
	   "  • %u is substituted with the username\n"
	   "  • %U is substituted with the SSH username\n"
	   "  • %p is substituted with Remmina profile name\n"
	   "  • %g is substituted with Remmina profile group name\n"
	   "  • %d is substituted with local date and time in ISO 8601 format\n");

/**
 * Array of RemminaProtocolSetting for advanced settings.
 * - Each item is composed by:
 *  1. RemminaProtocolSettingType for setting type.
 *  2. Setting name.
 *  3. Setting description.
 *  4. Compact disposition.
 *  5. Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO.
 *  6. Setting Tooltip.
 *
 */
static const RemminaProtocolSetting remmina_ssh_advanced_settings[] =
{
	{ REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_color_scheme",	  N_("Terminal colour scheme"),		      FALSE, ssh_terminal_palette, NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_charset",		  N_("Character set"),			      FALSE, ssh_charset_list,	   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	"ssh_proxycommand",	  N_("SSH Proxy Command"),		      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	"ssh_kex_algorithms",	  N_("KEX (Key Exchange) algorithms"),	      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	"ssh_ciphers",		  N_("Symmetric cipher client to server"),    FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	"ssh_hostkeytypes",	  N_("Preferred server host key types"),      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,   "sshlogfolder",		  N_("Folder for SSH session log"),	      FALSE, NULL,		   N_("Full path of an existing folder")					 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	"sshlogname",		  N_("Filename for SSH session log"),	      FALSE, NULL,		   log_tips									 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"sshlogenabled",	  N_("Log SSH session when exiting Remmina"), FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"sshsavesession",	  N_("Log SSH session asynchronously"),	      TRUE,  NULL,		   N_("Saving the session asynchronously may have a notable performance impact") },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"audiblebell",		  N_("Audible terminal bell"),		      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"ssh_forward_x11",	  N_("SSH X11 Forwarding"),			      TRUE,  NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"ssh_compression",	  N_("SSH compression"),		      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"disablepasswordstoring", N_("Don't remember passwords"),	      TRUE,  NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"ssh_stricthostkeycheck", N_("Strict host key checking"),	      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	"ssh_copy_on_select", N_("Copy text on selection"),	      FALSE, NULL,		   NULL										 },
	{ REMMINA_PROTOCOL_SETTING_TYPE_END,	NULL,			  NULL,					      FALSE, NULL,		   NULL										 }
};

/**
 * SSH Protocol plugin definition and features.
 *
 * Array used to define the SSH Protocol plugin Type, name, description, version
 * Plugin icon, features, initialization and closing functions.
 */
static RemminaProtocolPlugin remmina_plugin_ssh =
{
	REMMINA_PLUGIN_TYPE_PROTOCOL,                   /**< Type */
	"SSH",                                          /**< Name */
	N_("SSH - Secure Shell"),                       /**< Description */
	GETTEXT_PACKAGE,                                /**< Translation domain */
	VERSION,                                        /**< Version number */
	"org.remmina.Remmina-ssh-symbolic",             /**< Icon for normal connection */
	"org.remmina.Remmina-ssh-symbolic",             /**< Icon for SSH connection */
	remmina_ssh_basic_settings,                     /**< Array for basic settings */
	remmina_ssh_advanced_settings,                  /**< Array for advanced settings */
	REMMINA_PROTOCOL_SSH_SETTING_TUNNEL,            /**< SSH settings type */
	remmina_plugin_ssh_features,                    /**< Array for available features */
	remmina_plugin_ssh_init,                        /**< Plugin initialization */
	remmina_plugin_ssh_open_connection,             /**< Plugin open connection */
	remmina_plugin_ssh_close_connection,            /**< Plugin close connection */
	remmina_plugin_ssh_query_feature,               /**< Query for available features */
	remmina_plugin_ssh_call_feature,                /**< Call a feature */
	remmina_ssh_keystroke,                          /**< Send a keystroke */
	NULL,                                           /**< No screenshot support available */
	NULL,                                           /**< RCW map event */
	NULL                                            /**< RCW unmap event */
};


/*
 * this function is used for
 * - inserting into the list to became a sorted list [g_list_insert_sorted()]
 * - checking the list to avoid duplicate entries [g_list_find_custom()]
 */
static gint
compare(gconstpointer a, gconstpointer b)
{
	return strcmp((gchar *)a, (gchar *)b);
}

void
remmina_ssh_plugin_load_terminal_palettes(gpointer *ssh_terminal_palette_new)
{
	unsigned int preset_rec_size = sizeof(ssh_terminal_palette) / sizeof(gpointer);

	GError *error = NULL;
	GList *files = NULL;
	unsigned int rec_size = 0;
	/*
	 * count number of (all) files to reserve enough memory
	 */
	/* /usr/local/share/remmina */
	const gchar *const *dirs = g_get_system_data_dirs();

	unsigned int i = 0;

	for (i = 0; dirs[i] != NULL; ++i) {
		GDir *system_data_dir = NULL;
		gchar *remmina_dir = g_build_path("/", dirs[i], "remmina", "theme", NULL);
		system_data_dir = g_dir_open(remmina_dir, 0, &error);
		g_free(remmina_dir);
		// ignoring this error is OK, because the folder may not exist
		if (error) {
			g_error_free(error);
			error = NULL;
		} else {
			if (system_data_dir) {
				const gchar *filename;
				while ((filename = g_dir_read_name(system_data_dir))) {
					if (!g_file_test(filename, G_FILE_TEST_IS_DIR)) {
						if (g_str_has_suffix(filename, ".colors")) {
							gsize len = strrchr(filename, '.') - filename;
							gchar *menu_str = g_strndup(filename, len);
							if (g_list_find_custom(files, menu_str, compare) == NULL)
								files = g_list_insert_sorted(files, menu_str, compare);
						}
					}
				}
			}
		}
	}

	/* ~/.config/remmina/colors */
	gchar *remmina_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "theme", NULL);
	GDir *user_data_dir;

	user_data_dir = g_dir_open(remmina_dir, 0, &error);
	g_free(remmina_dir);
	if (error) {
		g_error_free(error);
		error = NULL;
	} else {
		if (user_data_dir) {
			const gchar *filename;
			while ((filename = g_dir_read_name(user_data_dir))) {
				if (!g_file_test(filename, G_FILE_TEST_IS_DIR)) {
					if (g_str_has_suffix(filename, ".colors")) {
						char *menu_str = g_malloc(strlen(filename) + 1);
						strcpy(menu_str, filename);
						char *t2 = strrchr(menu_str, '.');
						t2[0] = 0;
						if (g_list_find_custom(files, menu_str, compare) == NULL)
							files = g_list_insert_sorted(files, menu_str, compare);
					}
				}
			}
		}
	}

	rec_size = g_list_length(files) * 2;

	gpointer *color_palette = g_malloc((preset_rec_size + rec_size) * sizeof(gpointer));

	unsigned int field_idx = 0;

	*ssh_terminal_palette_new = color_palette;
	// preset with (old) static ssh_terminal_palette data
	for (; field_idx < preset_rec_size; field_idx++) {
		color_palette[field_idx] = ssh_terminal_palette[field_idx];
		if (!color_palette[field_idx])
			break;
	}

	GList *l_files = NULL;

	for (l_files = g_list_first(files); l_files != NULL; l_files = l_files->next) {
		gchar *menu_str = (gchar *)l_files->data;

		color_palette[field_idx++] = menu_str;
		color_palette[field_idx++] = menu_str;
	}
	g_list_free(files);

	color_palette[field_idx] = NULL;
}

void
remmina_ssh_plugin_register(void)
{
	TRACE_CALL(__func__);
	remmina_plugin_ssh_features[0].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_copy);
	remmina_plugin_ssh_features[1].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_paste);
	remmina_plugin_ssh_features[2].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_select_all);
	remmina_plugin_ssh_features[3].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_increase_font);
	remmina_plugin_ssh_features[4].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_decrease_font);
	remmina_plugin_ssh_features[5].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_search_text);

	remmina_plugin_service = &remmina_plugin_manager_service;

	RemminaProtocolSettingOpt *settings;

	// preset new settings with (old) static remmina_ssh_advanced_settings data
#if GLIB_CHECK_VERSION(2,68,0)
	settings = g_memdup2(remmina_ssh_advanced_settings, sizeof(remmina_ssh_advanced_settings));
#else
	settings = g_memdup(remmina_ssh_advanced_settings, sizeof(remmina_ssh_advanced_settings));
#endif

	// create dynamic advanced settings to made replacing of ssh_terminal_palette possible
	gpointer ssh_terminal_palette_new = NULL;

	remmina_ssh_plugin_load_terminal_palettes(&ssh_terminal_palette_new);

	settings[0].opt1 = ssh_terminal_palette_new;
	remmina_plugin_ssh.advanced_settings = (RemminaProtocolSetting *)settings;

	remmina_plugin_service->register_plugin((RemminaPlugin *)&remmina_plugin_ssh);

	ssh_threads_set_callbacks(ssh_threads_get_pthread());
	ssh_init();
}

#else

void remmina_ssh_plugin_register(void)
{
	TRACE_CALL(__func__);
}

#endif
