/*
 * Remmina - The GTK+ Remote Desktop Client
 * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
 *
 * 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 "gvnc_plugin_config.h"
#include "gvnc_plugin.h"

#include <vncdisplay.h>
#include <vncutil.h>
#include <vncaudiopulse.h>

#define GVNC_DEFAULT_PORT 5900

#ifndef VNC_CHECK_VERSION
# define VNC_CHECK_VERSION(a, b, c) 0
#endif
#if VNC_CHECK_VERSION(1, 2, 0)
# define HAVE_VNC_REMOTE_RESIZE
# define HAVE_VNC_POWER_CONTROL
#endif

enum {
	GVNC_PLUGIN_FEATURE_PREF_VIEWONLY = 1,
	GVNC_PLUGIN_FEATURE_DYNRESUPDATE,
	GVNC_PLUGIN_FEATURE_PREF_DISABLECLIPBOARD,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTDEL,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTBACKSPACE,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF1,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF2,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF3,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF4,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF5,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF6,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF7,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF8,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF9,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF10,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF11,
	GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF12,
#ifdef HAVE_VNC_POWER_CONTROL
	GVNC_PLUGIN_FEATURE_TOOL_REBOOT,
	GVNC_PLUGIN_FEATURE_TOOL_RESET,
	GVNC_PLUGIN_FEATURE_TOOL_SHUTDOWN,
#endif
	GVNC_PLUGIN_FEATURE_SCALE
};


static RemminaPluginService *remmina_plugin_service = NULL;

gchar* str_replace(const gchar *string, const gchar *search, const gchar *replacement)
{
	TRACE_CALL(__func__);
	gchar *str, **arr;

	g_return_val_if_fail(string != NULL, NULL);
	g_return_val_if_fail(search != NULL, NULL);

	if (replacement == NULL)
		replacement = "";

	arr = g_strsplit(string, search, -1);
	if (arr != NULL && arr[0] != NULL)
		str = g_strjoinv(replacement, arr);
	else
		str = g_strdup(string);

	g_strfreev(arr);
	return str;
}

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

	if (gpdata->vnc) {
		vnc_display_send_keys(VNC_DISPLAY(gpdata->vnc),
				      keystrokes,
				      keylen);
	}
}

#ifdef HAVE_VNC_POWER_CONTROL
static void gvnc_plugin_power_ctrl(RemminaProtocolWidget *gp, VncConnectionPowerAction action)
{
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	VncConnection *conn = vnc_display_get_connection(VNC_DISPLAY(gpdata->vnc));

	if (!vnc_connection_get_power_control(conn)) {
		REMMINA_PLUGIN_DEBUG("Power control not available on this server");
		return;
	}
	vnc_connection_power_control(conn, action);
}
#endif


static void gvnc_plugin_mouse_grab(GtkWidget *vncdisplay, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	VncGrabSequence *seq = vnc_display_get_grab_keys(VNC_DISPLAY(gpdata->vnc));
	gchar *seqstr = vnc_grab_sequence_as_string(seq);

	REMMINA_PLUGIN_DEBUG("Pointer grabbed: %s", seqstr);
}

static void gvnc_plugin_mouse_ungrab(GtkWidget *vncdisplay, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	REMMINA_PLUGIN_DEBUG("Pointer ungrabbed");
}

static void gvnc_plugin_on_vnc_error(GtkWidget *vncdisplay G_GNUC_UNUSED, const gchar *msg, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	REMMINA_PLUGIN_DEBUG("Error: %s\n", msg);
	/* "vnc-error" is always followed by "vnc-disconnected",
	 * so save the error for that signal */
	g_free(gpdata->error_msg);
	gpdata->error_msg = g_strdup(msg);
}

static gboolean gvnc_plugin_get_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd) __attribute__ ((unused));
static gboolean gvnc_plugin_get_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
{
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	const VncPixelFormat *currentFormat;

	if (!gpdata)
		return FALSE;

	/* Get current pixel format for server */
	currentFormat = vnc_connection_get_pixel_format(gpdata->conn);


	GdkPixbuf *pix = vnc_display_get_pixbuf(VNC_DISPLAY(gpdata->vnc));

	rpsd->width = gdk_pixbuf_get_width(pix);
	rpsd->height = gdk_pixbuf_get_height(pix);
	rpsd->bitsPerPixel = currentFormat->bits_per_pixel;
	rpsd->bytesPerPixel = rpsd->bitsPerPixel / 8;



	/* Returning TRUE instruct also the caller to deallocate rpsd->buffer */
	return TRUE;
}

void gvnc_plugin_paste_text(RemminaProtocolWidget *gp, const gchar *text)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	gchar *out;
	gsize a, b;
	GError *error = NULL;

	if (!gpdata) return;

	out = g_convert_with_fallback(text, -1, "iso8859-1//TRANSLIT", "utf-8", NULL, &a, &b, &error);
	if (out) {
		REMMINA_PLUGIN_DEBUG("Pasting text");
		vnc_display_client_cut_text(VNC_DISPLAY(gpdata->vnc), out);
		g_free(out);
	} else {
		REMMINA_PLUGIN_DEBUG("Error pasting text: %s", error->message);
		g_error_free(error);
	}
}

static void gvnc_plugin_clipboard_cb(GtkClipboard *cb, GdkEvent *event, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	gchar *text;

	REMMINA_PLUGIN_DEBUG("owner-change event received");

	if (cb && gtk_clipboard_get_owner(cb) == (GObject *)gp)
		return;

	text = gtk_clipboard_wait_for_text(cb);
	if (!text)
		return;

	gvnc_plugin_paste_text(gp, text);
	g_free(text);
}


/* text was actually requested */
static void gvnc_plugin_clipboard_copy(GtkClipboard *clipboard G_GNUC_UNUSED, GtkSelectionData *data, guint info G_GNUC_UNUSED, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	if (!gpdata) return;
	gtk_selection_data_set_text(data, gpdata->clipstr, -1);
	REMMINA_PLUGIN_DEBUG("Text copied");
}

static void gvnc_plugin_cut_text(VncDisplay *vnc G_GNUC_UNUSED, const gchar *text, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	REMMINA_PLUGIN_DEBUG("Got clipboard request for “%s”", text);

	GtkClipboard *cb;
	gsize a, b;
	GtkTargetEntry targets[] = {
		{ g_strdup("UTF8_STRING"),   0, 0 },
		{ g_strdup("COMPOUND_TEXT"), 0, 0 },
		{ g_strdup("TEXT"),	     0, 0 },
		{ g_strdup("STRING"),	     0, 0 },
	};

	if (!text)
		return;
	g_free(gpdata->clipstr);
	gpdata->clipstr = g_convert(text, -1, "utf-8", "iso8859-1", &a, &b, NULL);

	if (gpdata->clipstr) {
		cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);

		REMMINA_PLUGIN_DEBUG("setting clipboard with owner to owner %p", gp);
		gtk_clipboard_set_with_owner(cb,
					     targets,
					     G_N_ELEMENTS(targets),
					     (GtkClipboardGetFunc)gvnc_plugin_clipboard_copy,
					     NULL,
					     G_OBJECT(gp));
	}

}


static void gvnc_plugin_desktop_resize(GtkWidget *vncdisplay G_GNUC_UNUSED, int width, int height, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	REMMINA_PLUGIN_DEBUG("Remote desktop size changed to %dx%d\n", width, height);
	REMMINA_PLUGIN_DEBUG("Desktop size is set to %dx%d\n", gpdata->width, gpdata->height);
	remmina_plugin_service->protocol_plugin_set_width(gp, width);
	remmina_plugin_service->protocol_plugin_set_height(gp, height);

	remmina_plugin_service->protocol_plugin_desktop_resize(gp);
}

static void gvnc_plugin_on_bell(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	REMMINA_PLUGIN_DEBUG("Bell message received");
	RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);

	if (remmina_plugin_service->file_get_int(remminafile, "disableserverbell", FALSE))
		return;
	GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(gp));

	if (window)
		gdk_window_beep(window);
	REMMINA_PLUGIN_DEBUG("Beep emitted");
}
static void gvnc_plugin_update_scale_mode(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	RemminaScaleMode scaleMode = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp);

#if 0
	g_object_set(gpdata->display,
		     "scaling", (scaleMode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED),
		     "resize-guest", (scaleMode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES),
		     NULL);
#endif
	if (scaleMode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || scaleMode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
		vnc_display_set_scaling(VNC_DISPLAY(gpdata->vnc), TRUE);
	else
		vnc_display_set_scaling(VNC_DISPLAY(gpdata->vnc), FALSE);


	gpdata->width = vnc_display_get_width(VNC_DISPLAY(gpdata->vnc));
	gpdata->height = vnc_display_get_height(VNC_DISPLAY(gpdata->vnc));

	if (scaleMode != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE) {
		/* In scaled mode, the VncDisplay will get its dimensions from its parent */
		gtk_widget_set_size_request(GTK_WIDGET(gpdata->vnc), -1, -1);
	} else {
		/* In non scaled mode, the plugins forces dimensions of the VncDisplay */
#if 0
		g_object_get(gpdata->display_channel,
			     "width", &width,
			     "height", &height,
			     NULL);
		gtk_widget_set_size_request(GTK_WIDGET(gpdata->vnc), width, height);
#endif
		gtk_widget_set_size_request(GTK_WIDGET(gpdata->vnc), gpdata->width, gpdata->height);
	}
	remmina_plugin_service->protocol_plugin_update_align(gp);
}

static gboolean gvnc_plugin_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
	TRACE_CALL(__func__);

	return TRUE;
}

static void gvnc_plugin_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
	TRACE_CALL(__func__);

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

	guint keys[3];

	switch (feature->id) {
	case GVNC_PLUGIN_FEATURE_PREF_VIEWONLY:
		gpdata->viewonly = remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE);
		break;
	case GVNC_PLUGIN_FEATURE_DYNRESUPDATE:
	case GVNC_PLUGIN_FEATURE_SCALE:
		gvnc_plugin_update_scale_mode(gp);
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTDEL:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_Delete;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTBACKSPACE:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_BackSpace;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF1:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F1;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF2:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F2;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF3:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F3;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF4:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F4;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF5:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F5;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF6:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F6;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF7:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F7;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF9:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F9;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF10:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F10;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF11:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F11;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF12:
		keys[0] = GDK_KEY_Control_L;
		keys[1] = GDK_KEY_Alt_L;
		keys[2] = GDK_KEY_F12;
		gvnc_plugin_keystroke(gp, keys, G_N_ELEMENTS(keys));
		break;
#ifdef HAVE_VNC_POWER_CONTROL
	case GVNC_PLUGIN_FEATURE_TOOL_REBOOT:
		gvnc_plugin_power_ctrl(gp, VNC_CONNECTION_POWER_ACTION_REBOOT);
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_RESET:
		gvnc_plugin_power_ctrl(gp, VNC_CONNECTION_POWER_ACTION_RESET);
		break;
	case GVNC_PLUGIN_FEATURE_TOOL_SHUTDOWN:
		gvnc_plugin_power_ctrl(gp, VNC_CONNECTION_POWER_ACTION_SHUTDOWN);
		break;
#endif
	default:
		break;
	}
}

static void gvnc_plugin_auth_unsupported(VncDisplay *vnc G_GNUC_UNUSED, unsigned int authType, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	g_clear_pointer(&gpdata->error_msg, g_free);
	gchar *msg = g_strdup_printf(_("Unsupported authentication type %u"), authType);

	remmina_plugin_service->protocol_plugin_set_error(gp, "%s", msg);
	g_free(msg);
}

static void gvnc_plugin_auth_failure(VncDisplay *vnc G_GNUC_UNUSED, const gchar *reason, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	g_clear_pointer(&gpdata->error_msg, g_free);
	gchar *msg = g_strdup_printf(_("Authentication failure: %s"), reason);

	remmina_plugin_service->protocol_plugin_set_error(gp, "%s", msg);
	g_free(msg);
}

static gboolean gvnc_plugin_ask_auth(GtkWidget *vncdisplay, GValueArray *credList, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

	gint ret;
	gboolean disablepasswordstoring;
	gchar *s_username = NULL, *s_password = NULL;
	gboolean wantPassword = FALSE, wantUsername = FALSE;
	int i;
	gboolean save;


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


	GArray *credArray = g_array_sized_new(FALSE, TRUE, sizeof(GValue), credList->n_values);
	g_array_set_clear_func(credArray, (GDestroyNotify) g_value_unset);
	g_array_append_vals(credArray, credList->values, credList->n_values);

	REMMINA_PLUGIN_DEBUG("Got credential request for %d credential(s)\n", credArray->len);

	for (i = 0 ; i < credArray->len ; i++) {
		GValue *cred = &g_array_index(credArray, GValue, i);
		switch (g_value_get_enum(cred)) {
		case VNC_DISPLAY_CREDENTIAL_USERNAME:
			wantUsername = TRUE;
			break;
		case VNC_DISPLAY_CREDENTIAL_PASSWORD:
			wantPassword = TRUE;
			break;
		case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
			break;
		default:
			REMMINA_PLUGIN_DEBUG("Unsupported credential type %d", g_value_get_enum(cred));
			vnc_display_close(VNC_DISPLAY(gpdata->vnc));
			goto cleanup;
		}
	}
	disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);

	ret = remmina_plugin_service->protocol_plugin_init_auth(gp,
								(disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD)
								| (wantUsername ? REMMINA_MESSAGE_PANEL_FLAG_USERNAME : 0),
								_("Enter VNC authentication credentials"),
								(wantUsername ? remmina_plugin_service->file_get_string(remminafile, "username") : NULL),
								(wantPassword ? remmina_plugin_service->file_get_string(remminafile, "password") : NULL),
								NULL,
								NULL);
	if (ret == GTK_RESPONSE_OK) {
		s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
		s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
		remmina_plugin_service->file_set_string(remminafile, "username", s_username);

		save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
		if (save) {
			// User has requested to save credentials. We put the password
			// into remminafile->settings. It will be saved later, on successful connection, by
			// rcw.c
			remmina_plugin_service->file_set_string(remminafile, "password", s_password);
		} else {
			remmina_plugin_service->file_set_string(remminafile, "password", NULL);
		}

		for (i = 0 ; i < credArray->len ; i++) {
			GValue *cred = &g_array_index(credArray, GValue, i);
			switch (g_value_get_enum(cred)) {
				case VNC_DISPLAY_CREDENTIAL_USERNAME:
					if (!s_username ||
							vnc_display_set_credential(VNC_DISPLAY(gpdata->vnc),
								g_value_get_enum(cred),
								s_username)) {
						g_debug("Failed to set credential type %d", g_value_get_enum(cred));
						vnc_display_close(VNC_DISPLAY(gpdata->vnc));
					}
					break;
				case VNC_DISPLAY_CREDENTIAL_PASSWORD:
					if (!s_password ||
							vnc_display_set_credential(VNC_DISPLAY(gpdata->vnc),
								g_value_get_enum(cred),
								s_password)) {
					g_debug("Failed to set credential type %d", g_value_get_enum(cred));
					vnc_display_close(VNC_DISPLAY(gpdata->vnc));
				}
				break;
			case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
				if (vnc_display_set_credential(VNC_DISPLAY(gpdata->vnc),
							       g_value_get_enum(cred),
							       "remmina")) {
					g_debug("Failed to set credential type %d", g_value_get_enum(cred));
					vnc_display_close(VNC_DISPLAY(gpdata->vnc));
				}
				break;
			default:
				g_debug("Unsupported credential type %d", g_value_get_enum(cred));
				vnc_display_close(VNC_DISPLAY(gpdata->vnc));
			}
		}

		if (s_username) g_free(s_username);
		if (s_password) g_free(s_password);

		return TRUE;
	} else {
		return FALSE;
	}

cleanup:
	g_free(s_username);
	g_free(s_password);

	return TRUE;
}

static void gvnc_plugin_initialized(GtkWidget *vncdisplay, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

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

	REMMINA_PLUGIN_DEBUG("Connection initialized");
	g_return_if_fail(gpdata != NULL);
	REMMINA_PLUGIN_DEBUG("Presenting the window");

	VncAudioFormat format = {
		VNC_AUDIO_FORMAT_RAW_S32,
		2,
		44100,
	};

	REMMINA_PLUGIN_DEBUG("Gathering the VNC connection object");
	gpdata->conn = vnc_display_get_connection(VNC_DISPLAY(gpdata->vnc));
	g_return_if_fail(gpdata->conn != NULL);

	if (remmina_plugin_service->file_get_int(remminafile, "enableaudio", FALSE)) {
		REMMINA_PLUGIN_DEBUG("Setting up VNC audio channel");
		if (vnc_connection_set_audio_format(gpdata->conn, &format))
			REMMINA_PLUGIN_DEBUG("VNC audio format set");
		else {
			REMMINA_PLUGIN_DEBUG("VNC audio format returned an error");
			return;
		}

		if (vnc_connection_set_audio(gpdata->conn, VNC_AUDIO(gpdata->pa)))
			REMMINA_PLUGIN_DEBUG("VNC audio channel has been set");
		else {
			REMMINA_PLUGIN_DEBUG("VNC audio channel cannot be set");
			return;
		}
		REMMINA_PLUGIN_DEBUG("Enabling audio");
		if (vnc_connection_audio_enable(gpdata->conn))
			REMMINA_PLUGIN_DEBUG("Audio enabled");
		else
			REMMINA_PLUGIN_DEBUG("Audio cannot be enabled");
	}
	gpdata->width = vnc_display_get_width(VNC_DISPLAY(gpdata->vnc));
	gpdata->width = vnc_display_get_height(VNC_DISPLAY(gpdata->vnc));
}

static void gvnc_plugin_disconnected(VncDisplay *vnc G_GNUC_UNUSED, RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

	REMMINA_PLUGIN_DEBUG("[%s] Plugin disconnected", PLUGIN_NAME);
}
static gboolean gvnc_plugin_close_connection(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);

	REMMINA_PLUGIN_DEBUG("[%s] Plugin close connection", PLUGIN_NAME);

	if (gpdata) {
		if (gpdata->error_msg) g_free(gpdata->error_msg);
		if (gpdata->vnc)
			vnc_display_close(VNC_DISPLAY(gpdata->vnc));
	}

	/* Remove instance->context from gp object data to avoid double free */
	g_object_steal_data(G_OBJECT(gp), "plugin-data");
	remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
	return FALSE;
}

static void gvnc_plugin_init(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);
	GVncPluginData *gpdata;

	GtkClipboard *cb;

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

	gpdata->pa = NULL;

	REMMINA_PLUGIN_DEBUG("[%s] Plugin init", PLUGIN_NAME);

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

	if (remmina_plugin_service->file_get_int(remminafile, "gvncdebug", FALSE))
		vnc_util_set_debug(TRUE);

	gpdata->vnc = vnc_display_new();
	if (remmina_plugin_service->file_get_int(remminafile, "enableaudio", FALSE))
		gpdata->pa = vnc_audio_pulse_new();


	g_signal_connect(gpdata->vnc, "vnc-auth-credential",
			 G_CALLBACK(gvnc_plugin_ask_auth), gp);
	g_signal_connect(gpdata->vnc, "vnc-auth-failure",
			 G_CALLBACK(gvnc_plugin_auth_failure), gp);
	g_signal_connect(gpdata->vnc, "vnc-auth-unsupported",
			 G_CALLBACK(gvnc_plugin_auth_unsupported), gp);
	g_signal_connect(gpdata->vnc, "vnc-disconnected",
			 G_CALLBACK(gvnc_plugin_disconnected), gp);
	g_signal_connect(gpdata->vnc, "vnc-initialized",
			 G_CALLBACK(gvnc_plugin_initialized), gp);
	g_signal_connect(gpdata->vnc, "vnc-desktop-resize",
			 G_CALLBACK(gvnc_plugin_desktop_resize), gp);
	g_signal_connect(gpdata->vnc, "vnc-bell",
			 G_CALLBACK(gvnc_plugin_on_bell), gp);
	g_signal_connect(gpdata->vnc, "vnc-error",
			 G_CALLBACK(gvnc_plugin_on_vnc_error), gp);
	g_signal_connect(gpdata->vnc, "vnc-pointer-grab",
			 G_CALLBACK(gvnc_plugin_mouse_grab), gp);
	g_signal_connect(gpdata->vnc, "vnc-pointer-ungrab",
			 G_CALLBACK(gvnc_plugin_mouse_ungrab), gp);
	g_signal_connect(gpdata->vnc, "vnc-server-cut-text",
			 G_CALLBACK(gvnc_plugin_cut_text), gp);

	/* Setup the clipboard */
	cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
	gpdata->signal_clipboard = g_signal_connect(cb,
						    "owner-change",
						    G_CALLBACK(gvnc_plugin_clipboard_cb),
						    gp);
}

static gboolean gvnc_plugin_open_connection(RemminaProtocolWidget *gp)
{
	TRACE_CALL(__func__);

	gint port;
	gchar *host = NULL, *tunnel = NULL;
	GVncPluginData *gpdata = GET_PLUGIN_DATA(gp);
	RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);

	REMMINA_PLUGIN_DEBUG("[%s] Plugin open connection", PLUGIN_NAME);

	gpdata->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	gtk_container_add(GTK_CONTAINER(gp), gpdata->box);
	gtk_widget_set_hexpand(GTK_WIDGET(gpdata->vnc), TRUE);
	gtk_widget_set_vexpand(GTK_WIDGET(gpdata->vnc), TRUE);
	gtk_container_add(GTK_CONTAINER(gpdata->box), gpdata->vnc);

	remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->vnc);


	const gchar *address = remmina_plugin_service->file_get_string(remminafile, "server");
	if(strstr(g_strdup(address), "unix:///") != NULL) {
		REMMINA_PLUGIN_DEBUG("address contain unix:// -> %s", address);
		gchar *val = str_replace (address, "unix://", "");
		REMMINA_PLUGIN_DEBUG("address after cleaning = %s", val);
		gint fd = remmina_plugin_service->open_unix_sock(val);
		REMMINA_PLUGIN_DEBUG("Unix socket fd: %d", fd);
		gpdata->fd = fd;
		g_free(val);

	} else {
		/* Setup SSH tunnel if needed */
		tunnel = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, GVNC_DEFAULT_PORT, FALSE);
		if (!tunnel)
			return FALSE;
		remmina_plugin_service->get_server_port(tunnel,
				GVNC_DEFAULT_PORT,
				&host,
				&port);
	}



	gpdata->depth_profile = remmina_plugin_service->file_get_int(remminafile, "depth_profile", 24);
	gpdata->viewonly = remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE);
	vnc_display_set_depth(VNC_DISPLAY(gpdata->vnc), gpdata->depth_profile);
	if (gpdata->fd > 0)
		vnc_display_open_fd(VNC_DISPLAY(gpdata->vnc), gpdata->fd);
	else
		vnc_display_open_host(VNC_DISPLAY(gpdata->vnc), host, g_strdup_printf("%d", port));
	gpdata->lossy_encoding = remmina_plugin_service->file_get_int(remminafile, "lossy_encoding", FALSE);
	vnc_display_set_lossy_encoding(VNC_DISPLAY(gpdata->vnc), gpdata->lossy_encoding);
	vnc_display_set_shared_flag(VNC_DISPLAY(gpdata->vnc), gpdata->shared);

	if(host) g_free(host);
	if(tunnel) g_free(tunnel);

	/* TRUE Conflict with remmina? */
	vnc_display_set_keyboard_grab(VNC_DISPLAY(gpdata->vnc), FALSE);
	vnc_display_set_read_only(VNC_DISPLAY(gpdata->vnc), gpdata->viewonly);
	/* TRUE Conflict with remmina? */
	vnc_display_set_pointer_grab(VNC_DISPLAY(gpdata->vnc), FALSE);
	vnc_display_set_pointer_local(VNC_DISPLAY(gpdata->vnc), TRUE);

	vnc_display_set_force_size(VNC_DISPLAY(gpdata->vnc), FALSE);
	vnc_display_set_scaling(VNC_DISPLAY(gpdata->vnc), TRUE);
#ifdef HAVE_VNC_REMOTE_RESIZE
	vnc_display_set_allow_resize(VNC_DISPLAY(gpdata->vnc), TRUE);
	vnc_display_set_zoom_level(VNC_DISPLAY(gpdata->vnc), opt_zoom);
#endif
	remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
	gtk_widget_show_all(gpdata->box);
	return TRUE;
}

/* Array of key/value pairs for color depths */
static gpointer colordepth_list[] =
{
	"0", N_("Use server settings"),
	"1", N_("True colour (24 bits)"),
	"2", N_("High colour (16 bits)"),
	"3", N_("Low colour (8 bits)"),
	"4", N_("Ultra low colour (3 bits)"),
	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 gvnc_plugin_basic_settings[] =
{
	{ REMMINA_PROTOCOL_SETTING_TYPE_SERVER,	  "server",	    NULL,			FALSE, NULL,		NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password",	    N_("VNC password"),		FALSE, NULL,		NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_SELECT,	  "depth_profile",  N_("Colour depth"),		FALSE, colordepth_list, NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	  "lossy_encoding", N_("Use JPEG Compression"), TRUE,  NULL,		N_("This might not work on all VNC servers"), NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,	  "gvncdebug",	    N_("Enable GTK-VNC debug"), FALSE, NULL,		NULL, NULL, NULL },
	{ REMMINA_PROTOCOL_SETTING_TYPE_END,	  NULL,		    NULL,			FALSE, NULL,		NULL, NULL, NULL }
};

/* Array of RemminaProtocolSetting for advanced 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
 */
static const RemminaProtocolSetting gvnc_plugin_advanced_settings[] =
{
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard",	 N_("No clipboard sync"),	    TRUE,  NULL, NULL											},
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"),  FALSE, NULL, NULL											},
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverbell",	 N_("Ignore remote bell messages"), TRUE,  NULL, NULL											},
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enableaudio",		 N_("Enable audio channel"),	    FALSE, NULL, NULL											},
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly",		 N_("View only"),		    TRUE,  NULL, NULL											},
	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shared",		 N_("Shared connection"),	    TRUE,  NULL, N_("If the server should try to share the desktop by leaving other clients connected") },
	{ REMMINA_PROTOCOL_SETTING_TYPE_END,   NULL,			 NULL,				    TRUE,  NULL, NULL											}
};

/* Array for available features.
 * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
static const RemminaProtocolFeature gvnc_plugin_features[] =
{
	{ REMMINA_PROTOCOL_FEATURE_TYPE_PREF,	      GVNC_PLUGIN_FEATURE_PREF_VIEWONLY,	     GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly",	       N_("View only")	       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_PREF,	      GVNC_PLUGIN_FEATURE_PREF_DISABLECLIPBOARD,     GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "disableclipboard", N_("No clipboard sync") },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTDEL,	     N_("Send Ctrl+Alt+_Del"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTBACKSPACE, N_("Send Ctrl+Alt+_Backspace"),			   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF1,	     N_("Send Ctrl+Alt+_F1"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF2,	     N_("Send Ctrl+Alt+_F2"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF3,	     N_("Send Ctrl+Alt+_F3"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF4,	     N_("Send Ctrl+Alt+_F4"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF5,	     N_("Send Ctrl+Alt+_F5"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF6,	     N_("Send Ctrl+Alt+_F6"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF7,	     N_("Send Ctrl+Alt+_F7"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF8,	     N_("Send Ctrl+Alt+_F8"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF9,	     N_("Send Ctrl+Alt+_F9"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF10,	     N_("Send Ctrl+Alt+_F10"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF11,	     N_("Send Ctrl+Alt+_F11"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SENDCTRLALTF12,	     N_("Send Ctrl+Alt+_F12"),				   NULL,	       NULL		       },
#ifdef HAVE_VNC_POWER_CONTROL
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_REBOOT,		     N_("Reboot remote host"),				   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_RESET,		     N_("Reset remote host (hard reboot)"),		   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,	      GVNC_PLUGIN_FEATURE_TOOL_SHUTDOWN,	     N_("Shutdown remote host"),			   NULL,	       NULL		       },
#endif
	{ REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE, GVNC_PLUGIN_FEATURE_DYNRESUPDATE,		     NULL,						   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE,	      GVNC_PLUGIN_FEATURE_SCALE,		     NULL,						   NULL,	       NULL		       },
	{ REMMINA_PROTOCOL_FEATURE_TYPE_END,	      0,					     NULL,						   NULL,	       NULL		       }
};

/* Protocol plugin definition and features */
static RemminaProtocolPlugin remmina_plugin = {
	REMMINA_PLUGIN_TYPE_PROTOCOL,           // Type
	PLUGIN_NAME,                            // Name
	PLUGIN_DESCRIPTION,                     // Description
	GETTEXT_PACKAGE,                        // Translation domain
	PLUGIN_VERSION,                         // Version number
	PLUGIN_APPICON,                         // Icon for normal connection
	PLUGIN_APPICON,                         // Icon for SSH connection
	gvnc_plugin_basic_settings,             // Array for basic settings
	gvnc_plugin_advanced_settings,          // Array for advanced settings
	REMMINA_PROTOCOL_SSH_SETTING_TUNNEL,    // SSH settings type
	gvnc_plugin_features,                   // Array for available features
	gvnc_plugin_init,                       // Plugin initialization
	gvnc_plugin_open_connection,            // Plugin open connection
	gvnc_plugin_close_connection,           // Plugin close connection
	gvnc_plugin_query_feature,              // Query for available features
	gvnc_plugin_call_feature,               // Call a feature
	gvnc_plugin_keystroke,                  // Send a keystroke
	NULL,                                   // No screenshot support available
	//gvnc_plugin_get_screenshot,             // No screenshot support available
	NULL,                                   // RCW map event
	NULL                                    // RCW unmap event
};

G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
{
	TRACE_CALL(__func__);
	remmina_plugin_service = service;

	bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");

	if (!service->register_plugin((RemminaPlugin *)&remmina_plugin))
		return FALSE;

	return TRUE;
}
