summaryrefslogtreecommitdiffstats
path: root/plugins/vnc/vnc_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/vnc/vnc_plugin.c')
-rw-r--r--plugins/vnc/vnc_plugin.c2228
1 files changed, 2228 insertions, 0 deletions
diff --git a/plugins/vnc/vnc_plugin.c b/plugins/vnc/vnc_plugin.c
new file mode 100644
index 0000000..633a2a9
--- /dev/null
+++ b/plugins/vnc/vnc_plugin.c
@@ -0,0 +1,2228 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo
+ * 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 <gmodule.h>
+#include "vnc_plugin.h"
+#include <rfb/rfbclient.h>
+
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY 1
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY 2
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT 3
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH 4
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT 5
+#define REMMINA_PLUGIN_VNC_FEATURE_SCALE 6
+#define REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS 7
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL 8
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_COLOR 9
+#define REMMINA_PLUGIN_VNC_FEATURE_DYNRESUPDATE 10
+
+#define VNC_DEFAULT_PORT 5900
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginVncData *)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static int dot_cursor_x_hot = 2;
+static int dot_cursor_y_hot = 2;
+static const gchar *dot_cursor_xpm[] =
+{ "5 5 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ... ", ".+++.", ".+ +.", ".+++.", " ... " };
+
+
+#define LOCK_BUFFER(t) if (t) { CANCEL_DEFER } pthread_mutex_lock(&gpdata->buffer_mutex);
+#define UNLOCK_BUFFER(t) pthread_mutex_unlock(&gpdata->buffer_mutex); if (t) { CANCEL_ASYNC }
+
+struct onMainThread_cb_data {
+ enum { FUNC_UPDATE_SCALE } func;
+ GtkWidget * widget;
+ gint x, y, width, height;
+ RemminaProtocolWidget * gp;
+ gboolean scale;
+
+ /* Mutex for thread synchronization */
+ pthread_mutex_t mu;
+ /* Flag to catch cancellations */
+ gboolean cancelled;
+};
+
+static gboolean onMainThread_cb(struct onMainThread_cb_data *d)
+{
+ TRACE_CALL(__func__);
+ if (!d->cancelled) {
+ switch (d->func) {
+ case FUNC_UPDATE_SCALE:
+ remmina_plugin_vnc_update_scale(d->gp, d->scale);
+ break;
+ }
+ pthread_mutex_unlock(&d->mu);
+ } else {
+ /* thread has been cancelled, so we must free d memory here */
+ g_free(d);
+ }
+ return G_SOURCE_REMOVE;
+}
+
+
+static void onMainThread_cleanup_handler(gpointer data)
+{
+ TRACE_CALL(__func__);
+ struct onMainThread_cb_data *d = data;
+
+ d->cancelled = TRUE;
+}
+
+
+static void onMainThread_schedule_callback_and_wait(struct onMainThread_cb_data *d)
+{
+ TRACE_CALL(__func__);
+ d->cancelled = FALSE;
+ pthread_cleanup_push(onMainThread_cleanup_handler, d);
+ pthread_mutex_init(&d->mu, NULL);
+ pthread_mutex_lock(&d->mu);
+ gdk_threads_add_idle((GSourceFunc)onMainThread_cb, (gpointer)d);
+
+ pthread_mutex_lock(&d->mu);
+
+ pthread_cleanup_pop(0);
+ pthread_mutex_unlock(&d->mu);
+ pthread_mutex_destroy(&d->mu);
+}
+
+/**
+ * Function check_for_endianness() returns 1, if architecture
+ * is little endian, 0 in case of big endian.
+ */
+static gboolean check_for_endianness()
+{
+ unsigned int x = 1;
+ char *c = (char *)&x;
+
+ return (int)*c;
+}
+
+static void remmina_plugin_vnc_event_push(RemminaProtocolWidget *gp, gint event_type, gpointer p1, gpointer p2, gpointer p3)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaPluginVncEvent *event;
+
+ event = g_new(RemminaPluginVncEvent, 1);
+ event->event_type = event_type;
+ switch (event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_KEY:
+ event->event_data.key.keyval = GPOINTER_TO_UINT(p1);
+ event->event_data.key.pressed = GPOINTER_TO_INT(p2);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_POINTER:
+ event->event_data.pointer.x = GPOINTER_TO_INT(p1);
+ event->event_data.pointer.y = GPOINTER_TO_INT(p2);
+ event->event_data.pointer.button_mask = GPOINTER_TO_INT(p3);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ event->event_data.text.text = g_strdup((char *)p1);
+ break;
+ default:
+ break;
+ }
+
+ pthread_mutex_lock(&gpdata->vnc_event_queue_mutex);
+ g_queue_push_tail(gpdata->vnc_event_queue, event);
+ pthread_mutex_unlock(&gpdata->vnc_event_queue_mutex);
+
+ if (write(gpdata->vnc_event_pipe[1], "\0", 1)) {
+ /* Ignore */
+ }
+}
+
+static void remmina_plugin_vnc_event_free(RemminaPluginVncEvent *event)
+{
+ TRACE_CALL(__func__);
+ switch (event->event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ g_free(event->event_data.text.text);
+ break;
+ default:
+ break;
+ }
+ g_free(event);
+}
+
+static void remmina_plugin_vnc_event_free_all(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaPluginVncEvent *event;
+
+ /* This is called from main thread after plugin thread has
+ * been closed, so no queue locking is necessary here */
+ while ((event = g_queue_pop_head(gpdata->vnc_event_queue)) != NULL)
+ remmina_plugin_vnc_event_free(event);
+}
+
+static void remmina_plugin_vnc_scale_area(RemminaProtocolWidget *gp, gint *x, gint *y, gint *w, gint *h)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GtkAllocation widget_allocation;
+ gint width, height;
+ gint sx, sy, sw, sh;
+
+ if (gpdata->rgb_buffer == NULL)
+ return;
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ gtk_widget_get_allocation(GTK_WIDGET(gp), &widget_allocation);
+
+ if (widget_allocation.width == width && widget_allocation.height == height)
+ return; /* Same size, no scaling */
+
+ /* We have to extend the scaled region 2 scaled pixels, to avoid gaps */
+ sx = MIN(MAX(0, (*x) * widget_allocation.width / width - widget_allocation.width / width - 2), widget_allocation.width - 1);
+ sy = MIN(MAX(0, (*y) * widget_allocation.height / height - widget_allocation.height / height - 2), widget_allocation.height - 1);
+ sw = MIN(widget_allocation.width - sx, (*w) * widget_allocation.width / width + widget_allocation.width / width + 4);
+ sh = MIN(widget_allocation.height - sy, (*h) * widget_allocation.height / height + widget_allocation.height / height + 4);
+
+ *x = sx;
+ *y = sy;
+ *w = sw;
+ *h = sh;
+}
+
+static void remmina_plugin_vnc_update_scale(RemminaProtocolWidget *gp, gboolean scale)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ RemminaPluginVncData *gpdata;
+ gint width, height;
+
+ if (!remmina_plugin_service->is_main_thread()) {
+ struct onMainThread_cb_data *d;
+ d = (struct onMainThread_cb_data *)g_malloc(sizeof(struct onMainThread_cb_data));
+ d->func = FUNC_UPDATE_SCALE;
+ d->gp = gp;
+ d->scale = scale;
+ onMainThread_schedule_callback_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+ gpdata = GET_PLUGIN_DATA(gp);
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+ if (scale)
+ /* In scaled mode, drawing_area will get its dimensions from its parent */
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->drawing_area), -1, -1);
+ else
+ /* In non scaled mode, the plugins forces dimensions of drawing area */
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->drawing_area), width, height);
+
+ remmina_plugin_service->protocol_plugin_update_align(gp);
+}
+
+gboolean remmina_plugin_vnc_setcursor(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GdkCursor *cur;
+
+ LOCK_BUFFER(FALSE);
+ gpdata->queuecursor_handler = 0;
+
+ if (gpdata->queuecursor_surface) {
+ cur = gdk_cursor_new_from_surface(gdk_display_get_default(), gpdata->queuecursor_surface, gpdata->queuecursor_x,
+ gpdata->queuecursor_y);
+ gdk_window_set_cursor(gtk_widget_get_window(gpdata->drawing_area), cur);
+ g_object_unref(cur);
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ gpdata->queuecursor_surface = NULL;
+ } else {
+ gdk_window_set_cursor(gtk_widget_get_window(gpdata->drawing_area), NULL);
+ }
+ UNLOCK_BUFFER(FALSE);
+
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_queuecursor(RemminaProtocolWidget *gp, cairo_surface_t *surface, gint x, gint y)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->queuecursor_surface)
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ gpdata->queuecursor_surface = surface;
+ gpdata->queuecursor_x = x;
+ gpdata->queuecursor_y = y;
+ if (!gpdata->queuecursor_handler)
+ gpdata->queuecursor_handler = IDLE_ADD((GSourceFunc)remmina_plugin_vnc_setcursor, gp);
+}
+
+typedef struct _RemminaKeyVal {
+ guint keyval;
+ guint16 keycode;
+} RemminaKeyVal;
+
+/***************************** LibVNCClient related codes *********************************/
+
+static const uint32_t remmina_plugin_vnc_no_encrypt_auth_types[] =
+{ rfbNoAuth, rfbVncAuth, rfbMSLogon, 0 };
+
+static RemminaPluginVncEvent *remmina_plugin_vnc_event_queue_pop_head(RemminaPluginVncData *gpdata)
+{
+ RemminaPluginVncEvent *event;
+
+ CANCEL_DEFER;
+ pthread_mutex_lock(&gpdata->vnc_event_queue_mutex);
+
+ event = g_queue_pop_head(gpdata->vnc_event_queue);
+
+ pthread_mutex_unlock(&gpdata->vnc_event_queue_mutex);
+ CANCEL_ASYNC;
+
+ return event;
+}
+
+static void remmina_plugin_vnc_process_vnc_event(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncEvent *event;
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ rfbClient *cl;
+ gchar buf[100];
+
+ cl = (rfbClient *)gpdata->client;
+ while ((event = remmina_plugin_vnc_event_queue_pop_head(gpdata)) != NULL) {
+ if (cl) {
+ switch (event->event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_KEY:
+ SendKeyEvent(cl, event->event_data.key.keyval, event->event_data.key.pressed);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_POINTER:
+ SendPointerEvent(cl, event->event_data.pointer.x, event->event_data.pointer.y,
+ event->event_data.pointer.button_mask);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ if (event->event_data.text.text) {
+ rfbClientLog("sending clipboard text '%s'\n", event->event_data.text.text);
+ SendClientCutText(cl, event->event_data.text.text, strlen(event->event_data.text.text));
+ }
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN:
+ TextChatOpen(cl);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ TextChatSend(cl, event->event_data.text.text);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE:
+ TextChatClose(cl);
+ TextChatFinish(cl);
+ break;
+ default:
+ rfbClientLog("Ignoring VNC event: 0x%x\n", event->event_type);
+ break;
+ }
+ }
+ remmina_plugin_vnc_event_free(event);
+ }
+ if (read(gpdata->vnc_event_pipe[0], buf, sizeof(buf))) {
+ /* Ignore */
+ }
+}
+
+typedef struct _RemminaPluginVncCuttextParam {
+ RemminaProtocolWidget * gp;
+ gchar * text;
+ gint textlen;
+} RemminaPluginVncCuttextParam;
+
+static void remmina_plugin_vnc_update_quality(rfbClient *cl, gint quality)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaFile *remminafile;
+ gchar *enc = NULL;
+
+ /**
+ * "0", "Poor (fastest)"
+ * "1", "Medium"
+ * "2", "Good"
+ * "9", "Best (slowest)"
+ */
+ switch (quality) {
+ case 9:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "copyrect zlib hextile raw";
+ cl->appData.compressLevel = 1;
+ cl->appData.qualityLevel = 9;
+ break;
+ case 2:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
+ cl->appData.compressLevel = 2;
+ cl->appData.qualityLevel = 7;
+ break;
+ case 1:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
+ cl->appData.compressLevel = 3;
+ cl->appData.qualityLevel = 5;
+ break;
+ case 0:
+ default:
+ // bpp8 and tight encoding is not supported in libvnc
+ cl->appData.useBGR233 = 1;
+ cl->appData.encodingsString = "copyrect zrle ultra zlib hextile corre rre raw";
+ cl->appData.qualityLevel = 1;
+ break;
+ }
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ enc = g_strdup(remmina_plugin_service->file_get_string(remminafile, "encodings"));
+ if (enc) {
+ cl->appData.encodingsString = g_strdup(enc);
+ g_free (enc), enc = NULL;
+ }
+ gboolean tight = remmina_plugin_service->file_get_int (remminafile, "tightencoding", FALSE);
+ if (tight) {
+ if (!g_strrstr ( g_strdup(cl->appData.encodingsString), "tight\0")) {
+ cl->appData.encodingsString = g_strdup_printf("%s %s", "tight", g_strdup(cl->appData.encodingsString));
+ }
+ }
+
+ REMMINA_PLUGIN_DEBUG("Quality: %d", quality);
+ REMMINA_PLUGIN_DEBUG("Encodings: %s", cl->appData.encodingsString);
+}
+
+static void remmina_plugin_vnc_update_colordepth(rfbClient *cl, gint colordepth)
+{
+ TRACE_CALL(__func__);
+
+ cl->format.depth = colordepth;
+ cl->appData.requestedDepth = colordepth;
+
+ cl->format.trueColour = 1;
+ cl->format.bigEndian = check_for_endianness()?FALSE:TRUE;
+
+ switch (colordepth) {
+ case 8:
+ cl->format.depth = 8;
+ cl->format.bitsPerPixel = 8;
+ cl->format.blueMax = 3;
+ cl->format.blueShift = 6;
+ cl->format.greenMax = 7;
+ cl->format.greenShift = 3;
+ cl->format.redMax = 7;
+ cl->format.redShift = 0;
+ break;
+ case 16:
+ cl->format.depth = 15;
+ cl->format.bitsPerPixel = 16;
+ cl->format.redShift = 11;
+ cl->format.greenShift = 6;
+ cl->format.blueShift = 1;
+ cl->format.redMax = 31;
+ cl->format.greenMax = 31;
+ cl->format.blueMax = 31;
+ break;
+ case 32:
+ default:
+ cl->format.depth = 24;
+ cl->format.bitsPerPixel = 32;
+ cl->format.blueShift = 0;
+ cl->format.redShift = 16;
+ cl->format.greenShift = 8;
+ cl->format.blueMax = 0xff;
+ cl->format.redMax = 0xff;
+ cl->format.greenMax = 0xff;
+ break;
+ }
+
+ rfbClientLog("colordepth = %d\n", colordepth);
+ rfbClientLog("format.depth = %d\n", cl->format.depth);
+ rfbClientLog("format.bitsPerPixel = %d\n", cl->format.bitsPerPixel);
+ rfbClientLog("format.blueShift = %d\n", cl->format.blueShift);
+ rfbClientLog("format.redShift = %d\n", cl->format.redShift);
+ rfbClientLog("format.trueColour = %d\n", cl->format.trueColour);
+ rfbClientLog("format.greenShift = %d\n", cl->format.greenShift);
+ rfbClientLog("format.blueMax = %d\n", cl->format.blueMax);
+ rfbClientLog("format.redMax = %d\n", cl->format.redMax);
+ rfbClientLog("format.greenMax = %d\n", cl->format.greenMax);
+ rfbClientLog("format.bigEndian = %d\n", cl->format.bigEndian);
+}
+
+static rfbBool remmina_plugin_vnc_rfb_allocfb(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint width, height, depth, size;
+ gboolean scale;
+ cairo_surface_t *new_surface, *old_surface;
+
+ width = cl->width;
+ height = cl->height;
+ depth = cl->format.bitsPerPixel;
+ size = width * height * (depth / 8);
+
+ new_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS)
+ return FALSE;
+ old_surface = gpdata->rgb_buffer;
+
+ LOCK_BUFFER(TRUE);
+
+ remmina_plugin_service->protocol_plugin_set_width(gp, width);
+ remmina_plugin_service->protocol_plugin_set_height(gp, height);
+
+ gpdata->rgb_buffer = new_surface;
+
+ if (gpdata->vnc_buffer)
+ g_free(gpdata->vnc_buffer);
+ gpdata->vnc_buffer = (guchar *)g_malloc(size);
+ cl->frameBuffer = gpdata->vnc_buffer;
+
+ UNLOCK_BUFFER(TRUE);
+
+ if (old_surface)
+ cairo_surface_destroy(old_surface);
+
+ scale = (remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE);
+ remmina_plugin_vnc_update_scale(gp, scale);
+
+ /* Notify window of change so that scroll border can be hidden or shown if needed */
+ remmina_plugin_service->protocol_plugin_desktop_resize(gp);
+
+ /* Refresh the client’s updateRect - bug in xvncclient */
+ cl->updateRect.w = width;
+ cl->updateRect.h = height;
+
+ return TRUE;
+}
+
+static gint remmina_plugin_vnc_bits(gint n)
+{
+ TRACE_CALL(__func__);
+ gint b = 0;
+
+ while (n) {
+ b++;
+ n >>= 1;
+ }
+ return b ? b : 1;
+}
+
+static gboolean remmina_plugin_vnc_queue_draw_area_real(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint x, y, w, h;
+
+ if (GTK_IS_WIDGET(gp) && gpdata->connected) {
+ LOCK_BUFFER(FALSE);
+ x = gpdata->queuedraw_x;
+ y = gpdata->queuedraw_y;
+ w = gpdata->queuedraw_w;
+ h = gpdata->queuedraw_h;
+ gpdata->queuedraw_handler = 0;
+ UNLOCK_BUFFER(FALSE);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(gp), x, y, w, h);
+ }
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_queue_draw_area(RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint nx2, ny2, ox2, oy2;
+
+ LOCK_BUFFER(TRUE);
+ if (gpdata->queuedraw_handler) {
+ nx2 = x + w;
+ ny2 = y + h;
+ ox2 = gpdata->queuedraw_x + gpdata->queuedraw_w;
+ oy2 = gpdata->queuedraw_y + gpdata->queuedraw_h;
+ gpdata->queuedraw_x = MIN(gpdata->queuedraw_x, x);
+ gpdata->queuedraw_y = MIN(gpdata->queuedraw_y, y);
+ gpdata->queuedraw_w = MAX(ox2, nx2) - gpdata->queuedraw_x;
+ gpdata->queuedraw_h = MAX(oy2, ny2) - gpdata->queuedraw_y;
+ } else {
+ gpdata->queuedraw_x = x;
+ gpdata->queuedraw_y = y;
+ gpdata->queuedraw_w = w;
+ gpdata->queuedraw_h = h;
+ gpdata->queuedraw_handler = IDLE_ADD((GSourceFunc)remmina_plugin_vnc_queue_draw_area_real, gp);
+ }
+ UNLOCK_BUFFER(TRUE);
+}
+
+static void remmina_plugin_vnc_rfb_fill_buffer(rfbClient *cl, guchar *dest, gint dest_rowstride, guchar *src,
+ gint src_rowstride, guchar *mask, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ guchar *srcptr;
+ gint bytesPerPixel;
+ guint32 src_pixel;
+ gint ix, iy;
+ gint i;
+ guchar c;
+ gint rs, gs, bs, rm, gm, bm, rl, gl, bl, rr, gr, br;
+ gint r;
+ guint32 *destptr;
+
+ union {
+ struct {
+ guchar a, r, g, b;
+ } colors;
+ guint32 argb;
+ } dst_pixel;
+
+ bytesPerPixel = cl->format.bitsPerPixel / 8;
+ switch (cl->format.bitsPerPixel) {
+ case 32:
+ /* The following codes fill in the Alpha channel swap red/green value */
+ for (iy = 0; iy < h; iy++) {
+ destptr = (guint32 *)(dest + iy * dest_rowstride);
+ srcptr = src + iy * src_rowstride;
+ for (ix = 0; ix < w; ix++) {
+ if (!mask || *mask++) {
+ dst_pixel.colors.a = 0xff;
+ dst_pixel.colors.r = *(srcptr + 2);
+ dst_pixel.colors.g = *(srcptr + 1);
+ dst_pixel.colors.b = *srcptr;
+ *destptr++ = ntohl(dst_pixel.argb);
+ } else {
+ *destptr++ = 0;
+ }
+ srcptr += 4;
+ }
+ }
+ break;
+ default:
+ rm = cl->format.redMax;
+ gm = cl->format.greenMax;
+ bm = cl->format.blueMax;
+ rr = remmina_plugin_vnc_bits(rm);
+ gr = remmina_plugin_vnc_bits(gm);
+ br = remmina_plugin_vnc_bits(bm);
+ rl = 8 - rr;
+ gl = 8 - gr;
+ bl = 8 - br;
+ rs = cl->format.redShift;
+ gs = cl->format.greenShift;
+ bs = cl->format.blueShift;
+ for (iy = 0; iy < h; iy++) {
+ destptr = (guint32 *)(dest + iy * dest_rowstride);
+ srcptr = src + iy * src_rowstride;
+ for (ix = 0; ix < w; ix++) {
+ src_pixel = 0;
+ for (i = 0; i < bytesPerPixel; i++)
+ src_pixel += (*srcptr++) << (8 * i);
+
+ if (!mask || *mask++) {
+ dst_pixel.colors.a = 0xff;
+ c = (guchar)((src_pixel >> rs) & rm) << rl;
+ for (r = rr; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.r = c;
+ c = (guchar)((src_pixel >> gs) & gm) << gl;
+ for (r = gr; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.g = c;
+ c = (guchar)((src_pixel >> bs) & bm) << bl;
+ for (r = br; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.b = c;
+ *destptr++ = ntohl(dst_pixel.argb);
+ } else {
+ *destptr++ = 0;
+ }
+ }
+ }
+ break;
+ }
+}
+
+static void remmina_plugin_vnc_rfb_updatefb(rfbClient *cl, int x, int y, int w, int h)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint bytesPerPixel;
+ gint rowstride;
+ gint width;
+
+ LOCK_BUFFER(TRUE);
+
+ if (w >= 1 || h >= 1) {
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ bytesPerPixel = cl->format.bitsPerPixel / 8;
+ rowstride = cairo_image_surface_get_stride(gpdata->rgb_buffer);
+ cairo_surface_flush(gpdata->rgb_buffer);
+ remmina_plugin_vnc_rfb_fill_buffer(cl, cairo_image_surface_get_data(gpdata->rgb_buffer) + y * rowstride + x * 4,
+ rowstride, gpdata->vnc_buffer + ((y * width + x) * bytesPerPixel), width * bytesPerPixel, NULL,
+ w, h);
+ cairo_surface_mark_dirty(gpdata->rgb_buffer);
+ }
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE))
+ remmina_plugin_vnc_scale_area(gp, &x, &y, &w, &h);
+
+ UNLOCK_BUFFER(TRUE);
+
+ remmina_plugin_vnc_queue_draw_area(gp, x, y, w, h);
+}
+
+static void remmina_plugin_vnc_rfb_finished(rfbClient *cl) __attribute__ ((unused));
+static void remmina_plugin_vnc_rfb_finished(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("FinishedFrameBufferUpdate");
+}
+
+static void remmina_plugin_vnc_rfb_led_state(rfbClient *cl, int value, int pad)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Led state - value: %d, pad: %d", value, pad);
+}
+
+static gboolean remmina_plugin_vnc_queue_cuttext(RemminaPluginVncCuttextParam *param)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = param->gp;
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GDateTime *t;
+ glong diff;
+ const char *cur_charset;
+ gchar *text;
+ gsize br, bw;
+
+ if (GTK_IS_WIDGET(gp) && gpdata->connected) {
+ t = g_date_time_new_now_utc();
+ diff = g_date_time_difference(t, gpdata->clipboard_timer) / 100000; // tenth of second
+ if (diff >= 10) {
+ g_date_time_unref(gpdata->clipboard_timer);
+ gpdata->clipboard_timer = t;
+ /* Convert text from VNC latin-1 to current GTK charset (usually UTF-8) */
+ g_get_charset(&cur_charset);
+ text = g_convert_with_fallback(param->text, param->textlen, cur_charset, "ISO-8859-1", "?", &br, &bw, NULL);
+ gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), text, bw);
+ g_free(text);
+ } else {
+ g_date_time_unref(t);
+ }
+ }
+ g_free(param->text);
+ g_free(param);
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_rfb_cuttext(rfbClient *cl, const char *text, int textlen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncCuttextParam *param;
+
+ param = g_new(RemminaPluginVncCuttextParam, 1);
+ param->gp = (RemminaProtocolWidget *)rfbClientGetClientData(cl, NULL);
+ param->text = g_malloc(textlen);
+ memcpy(param->text, text, textlen);
+ param->textlen = textlen;
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_queue_cuttext, param);
+}
+
+static char *
+remmina_plugin_vnc_rfb_password(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gchar *pwd = NULL;
+
+ gpdata->auth_called = TRUE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (gpdata->auth_first)
+ pwd = g_strdup(remmina_plugin_service->file_get_string(remminafile, "password"));
+ if (!pwd) {
+ gboolean save;
+ gint ret;
+ gboolean 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),
+ _("Enter VNC password"),
+ NULL,
+ remmina_plugin_service->file_get_string(remminafile, "password"),
+ NULL,
+ NULL);
+ if (ret != GTK_RESPONSE_OK) {
+ gpdata->connected = FALSE;
+ return NULL;
+ }
+ pwd = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+ if (save)
+ remmina_plugin_service->file_set_string(remminafile, "password", pwd);
+ else
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+ }
+ return pwd;
+}
+
+static rfbCredential *
+remmina_plugin_vnc_rfb_credential(rfbClient *cl, int credentialType)
+{
+ TRACE_CALL(__func__);
+ rfbCredential *cred;
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gint ret;
+ gchar *s1, *s2;
+ gboolean disablepasswordstoring;
+
+ gpdata->auth_called = TRUE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ cred = g_new0(rfbCredential, 1);
+
+ switch (credentialType) {
+ case rfbCredentialTypeUser:
+
+ s1 = g_strdup(remmina_plugin_service->file_get_string(remminafile, "username"));
+
+ s2 = g_strdup(remmina_plugin_service->file_get_string(remminafile, "password"));
+
+ if (gpdata->auth_first && s1 && s2) {
+ cred->userCredential.username = s1;
+ cred->userCredential.password = s2;
+ } else {
+ g_free(s1);
+ g_free(s2);
+
+ 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) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME,
+ _("Enter VNC authentication credentials"),
+ remmina_plugin_service->file_get_string(remminafile, "username"),
+ remmina_plugin_service->file_get_string(remminafile, "password"),
+ NULL,
+ NULL);
+ if (ret == GTK_RESPONSE_OK) {
+ gboolean save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+ cred->userCredential.username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ cred->userCredential.password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ if (save) {
+ remmina_plugin_service->file_set_string(remminafile, "username", cred->userCredential.username);
+ remmina_plugin_service->file_set_string(remminafile, "password", cred->userCredential.password);
+ } else {
+ remmina_plugin_service->file_set_string(remminafile, "username", NULL);
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+ }
+ } else {
+ g_free(cred);
+ cred = NULL;
+ gpdata->connected = FALSE;
+ }
+ }
+ break;
+
+ case rfbCredentialTypeX509:
+ if (gpdata->auth_first &&
+ remmina_plugin_service->file_get_string(remminafile, "cacert")) {
+ cred->x509Credential.x509CACertFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "cacert"));
+ cred->x509Credential.x509CACrlFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "cacrl"));
+ cred->x509Credential.x509ClientCertFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "clientcert"));
+ cred->x509Credential.x509ClientKeyFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "clientkey"));
+ } else {
+ ret = remmina_plugin_service->protocol_plugin_init_authx509(gp);
+
+ if (ret == GTK_RESPONSE_OK) {
+ cred->x509Credential.x509CACertFile = remmina_plugin_service->protocol_plugin_init_get_cacert(gp);
+ cred->x509Credential.x509CACrlFile = remmina_plugin_service->protocol_plugin_init_get_cacrl(gp);
+ cred->x509Credential.x509ClientCertFile = remmina_plugin_service->protocol_plugin_init_get_clientcert(gp);
+ cred->x509Credential.x509ClientKeyFile = remmina_plugin_service->protocol_plugin_init_get_clientkey(gp);
+ } else {
+ g_free(cred);
+ cred = NULL;
+ gpdata->connected = FALSE;
+ }
+ }
+ break;
+
+ default:
+ g_free(cred);
+ cred = NULL;
+ break;
+ }
+ return cred;
+}
+
+static void remmina_plugin_vnc_rfb_cursor_shape(rfbClient *cl, int xhot, int yhot, int width, int height, int bytesPerPixel)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (!gtk_widget_get_window(GTK_WIDGET(gp)))
+ return;
+
+ if (width && height) {
+ gint stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
+ guchar *data = g_malloc(stride * height);
+ remmina_plugin_vnc_rfb_fill_buffer(cl, data, stride, cl->rcSource,
+ width * cl->format.bitsPerPixel / 8, cl->rcMask, width, height);
+ cairo_surface_t *surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ g_free(data);
+ return;
+ }
+ if (cairo_surface_set_user_data(surface, NULL, NULL, g_free) != CAIRO_STATUS_SUCCESS) {
+ g_free(data);
+ return;
+ }
+
+ LOCK_BUFFER(TRUE);
+ remmina_plugin_vnc_queuecursor(gp, surface, xhot, yhot);
+ UNLOCK_BUFFER(TRUE);
+ }
+}
+
+static void remmina_plugin_vnc_rfb_bell(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Bell message received");
+ RemminaProtocolWidget *gp;
+ RemminaFile *remminafile;
+ GdkWindow *window;
+
+ gp = (RemminaProtocolWidget *)(rfbClientGetClientData(cl, NULL));
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "disableserverbell", FALSE))
+ return;
+
+ window = gtk_widget_get_window(GTK_WIDGET(gp));
+
+ if (window)
+ gdk_window_beep(window);
+ REMMINA_PLUGIN_DEBUG("Beep emitted");
+}
+
+/* Translate known VNC messages. It’s for intltool only, not for gcc */
+#ifdef __DO_NOT_COMPILE_ME__
+N_("Unable to connect to VNC server")
+N_("Couldn’t convert '%s' to host address")
+N_("VNC connection failed: %s")
+N_("Your connection has been rejected.")
+#endif
+/** @todo We only store the last message at this moment. */
+#define MAX_ERROR_LENGTH 1000
+static gchar vnc_error[MAX_ERROR_LENGTH + 1];
+static gboolean vnc_encryption_disable_requested;
+
+static void remmina_plugin_vnc_rfb_output(const char *format, ...)
+{
+ TRACE_CALL(__func__);
+ gchar *f, *p, *ff;
+
+ if (!rfbEnableClientLogging)
+ return;
+
+ va_list args;
+ va_start(args, format);
+ /* eliminate the last \n */
+ f = g_strdup(format);
+ if (f[strlen(f) - 1] == '\n') f[strlen(f) - 1] = '\0';
+
+ if (g_strcmp0(f, "VNC connection failed: %s") == 0) {
+ p = va_arg(args, gchar *);
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, _(f), _(p));
+ } else if (g_strcmp0(f, "The VNC server requested an unknown authentication method. %s") == 0) {
+ p = va_arg(args, gchar *);
+ if (vnc_encryption_disable_requested) {
+ ff = g_strconcat(_("The VNC server requested an unknown authentication method. %s"),
+ ". ",
+ _("Please retry after turning on encryption for this profile."),
+ NULL);
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, ff, p);
+ g_free(ff);
+ } else {
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, _(f), p);
+ }
+ } else {
+ g_vsnprintf(vnc_error, MAX_ERROR_LENGTH, _(f), args);
+ }
+ g_free(f);
+ va_end(args);
+
+ REMMINA_PLUGIN_DEBUG("VNC returned: %s", vnc_error);
+}
+
+static void remmina_plugin_vnc_chat_on_send(RemminaProtocolWidget *gp, const gchar *text)
+{
+ TRACE_CALL(__func__);
+ gchar *ptr;
+
+ /* Need to add a line-feed for UltraVNC */
+ ptr = g_strdup_printf("%s\n", text);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND, ptr, NULL, NULL);
+ g_free(ptr);
+}
+
+static void remmina_plugin_vnc_chat_on_destroy(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE, NULL, NULL, NULL);
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin drawing_area widget */
+static void remmina_plugin_vnc_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->drawing_area,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_plugin_vnc_close_chat(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->protocol_plugin_chat_close(gp);
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_open_chat(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ rfbClient *cl;
+
+ cl = (rfbClient *)gpdata->client;
+
+ remmina_plugin_service->protocol_plugin_chat_open(gp, cl->desktopName, remmina_plugin_vnc_chat_on_send,
+ remmina_plugin_vnc_chat_on_destroy);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN, NULL, NULL, NULL);
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_rfb_chat(rfbClient *cl, int value, char *text)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp;
+
+ gp = (RemminaProtocolWidget *)(rfbClientGetClientData(cl, NULL));
+ switch (value) {
+ case rfbTextChatOpen:
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_open_chat, gp);
+ break;
+ case rfbTextChatClose:
+ /* Do nothing… but wait for the next rfbTextChatFinished signal */
+ break;
+ case rfbTextChatFinished:
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_close_chat, gp);
+ break;
+ default:
+ /* value is the text length */
+ remmina_plugin_service->protocol_plugin_chat_receive(gp, text);
+ break;
+ }
+}
+
+static gboolean remmina_plugin_vnc_incoming_connection(RemminaProtocolWidget *gp, rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ fd_set fds;
+
+ /**
+ * @fixme This may fail or not working as expected with multiple network interfaces,
+ * change with ListenAtTcpPortAndAddress
+ */
+ gpdata->listen_sock = ListenAtTcpPort(cl->listenPort);
+ if (gpdata->listen_sock < 0)
+ return FALSE;
+
+ remmina_plugin_service->protocol_plugin_init_show_listen(gp, cl->listenPort);
+
+ remmina_plugin_service->protocol_plugin_start_reverse_tunnel(gp, cl->listenPort);
+
+ FD_ZERO(&fds);
+ if (gpdata->listen_sock >= 0)
+ FD_SET(gpdata->listen_sock, &fds);
+
+ select(gpdata->listen_sock + 1, &fds, NULL, NULL, NULL);
+
+ if (!FD_ISSET(gpdata->listen_sock, &fds)) {
+ close(gpdata->listen_sock);
+ gpdata->listen_sock = -1;
+ return FALSE;
+ }
+
+ if (FD_ISSET(gpdata->listen_sock, &fds))
+ cl->sock = AcceptTcpConnection(gpdata->listen_sock);
+ if (cl->sock >= 0) {
+ close(gpdata->listen_sock);
+ gpdata->listen_sock = -1;
+ }
+ if (cl->sock < 0 || !SetNonBlocking(cl->sock))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static gboolean remmina_plugin_vnc_main_loop(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint ret;
+ gint i;
+ rfbClient *cl;
+ fd_set fds;
+ struct timeval timeout;
+
+ if (!gpdata->connected) {
+ gpdata->running = FALSE;
+ return FALSE;
+ }
+
+ cl = (rfbClient *)gpdata->client;
+
+ /*
+ * Do not explicitly wait while data is on the buffer, see:
+ * - https://jira.glyptodon.com/browse/GUAC-1056
+ * - https://jira.glyptodon.com/browse/GUAC-1056?focusedCommentId=14348&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14348
+ * - https://github.com/apache/guacamole-server/blob/67680bd2d51e7949453f0f7ffc7f4234a1136715/src/protocols/vnc/vnc.c#L155
+ */
+ if (cl->buffered)
+ goto handle_buffered;
+
+ timeout.tv_sec = 10;
+ timeout.tv_usec = 0;
+ FD_ZERO(&fds);
+ FD_SET(cl->sock, &fds);
+ FD_SET(gpdata->vnc_event_pipe[0], &fds);
+ ret = select(MAX(cl->sock, gpdata->vnc_event_pipe[0]) + 1, &fds, NULL, NULL, &timeout);
+
+ /* Sometimes it returns <0 when opening a modal dialog in other window. Absolutely weird */
+ /* So we continue looping anyway */
+ if (ret <= 0)
+ return TRUE;
+
+ if (FD_ISSET(gpdata->vnc_event_pipe[0], &fds))
+ remmina_plugin_vnc_process_vnc_event(gp);
+ if (FD_ISSET(cl->sock, &fds)) {
+ i = WaitForMessage(cl, 500);
+ if (i < 0)
+ return TRUE;
+handle_buffered:
+ if (!HandleRFBServerMessage(cl)) {
+ gpdata->running = FALSE;
+ if (gpdata->connected && !remmina_plugin_service->protocol_plugin_is_closed(gp))
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ rfbClient *cl = NULL;
+ gchar *host;
+ gchar *s = NULL;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ gpdata->running = TRUE;
+
+ rfbClientLog = rfbClientErr = remmina_plugin_vnc_rfb_output;
+
+ gint colordepth = remmina_plugin_service->file_get_int(remminafile, "colordepth", 32);
+ gint quality = remmina_plugin_service->file_get_int(remminafile, "quality", 9);
+
+ while (gpdata->connected) {
+ gpdata->auth_called = FALSE;
+
+ host = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, VNC_DEFAULT_PORT, TRUE);
+
+ if (host == NULL) {
+ REMMINA_PLUGIN_DEBUG("host is null");
+ gpdata->connected = FALSE;
+ break;
+ }
+
+ /* int bitsPerSample,int samplesPerPixel, int bytesPerPixel */
+ switch (colordepth) {
+ case 8:
+ cl = rfbGetClient(2, 3, 1);
+ break;
+ case 15:
+ case 16:
+ cl = rfbGetClient(5, 3, 2);
+ break;
+ case 24:
+ cl = rfbGetClient(6, 3, 3);
+ break;
+ case 32:
+ default:
+ cl = rfbGetClient(8, 3, 4);
+ break;
+ }
+ REMMINA_PLUGIN_DEBUG("Color depth: %d", colordepth);
+ cl->MallocFrameBuffer = remmina_plugin_vnc_rfb_allocfb;
+ cl->canHandleNewFBSize = TRUE;
+ cl->GetPassword = remmina_plugin_vnc_rfb_password;
+ cl->GetCredential = remmina_plugin_vnc_rfb_credential;
+ cl->GotFrameBufferUpdate = remmina_plugin_vnc_rfb_updatefb;
+ /**
+ * @fixme we have to implement FinishedFrameBufferUpdate
+ * This is to know when the server has finished to send a batch of frame buffer
+ * updates.
+ * cl->FinishedFrameBufferUpdate = remmina_plugin_vnc_rfb_finished;
+ */
+ /**
+ * @fixme we have to implement HandleKeyboardLedState
+ * cl->HandleKeyboardLedState = remmina_plugin_vnc_rfb_led_state
+ */
+ cl->HandleKeyboardLedState = remmina_plugin_vnc_rfb_led_state;
+ cl->GotXCutText = (
+ remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ?
+ NULL : remmina_plugin_vnc_rfb_cuttext);
+ cl->GotCursorShape = remmina_plugin_vnc_rfb_cursor_shape;
+ cl->Bell = remmina_plugin_vnc_rfb_bell;
+ cl->HandleTextChat = remmina_plugin_vnc_rfb_chat;
+ /**
+ * @fixme we have to implement HandleXvpMsg
+ * cl->HandleXvpMsg = remmina_plugin_vnc_rfb_handle_xvp;
+ */
+
+ rfbClientSetClientData(cl, NULL, gp);
+
+ if (host[0] == '\0') {
+ cl->serverHost = g_strdup(host);
+ cl->listenSpecified = TRUE;
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_tunnel_enabled", FALSE))
+ /* When we use reverse tunnel, the local port does not really matter.
+ * Hardcode a default port just in case the remote port is customized
+ * to a privilege port then we will have problem listening. */
+ cl->listenPort = 5500;
+ else
+ cl->listenPort = remmina_plugin_service->file_get_int(remminafile, "listenport", 5500);
+
+ remmina_plugin_vnc_incoming_connection(gp, cl);
+ } else {
+ if (strstr(host, "unix://") == host) {
+ cl->serverHost = g_strdup(host + strlen("unix://"));
+ cl->serverPort = 0;
+ } else {
+ remmina_plugin_service->get_server_port(host, VNC_DEFAULT_PORT, &s, &cl->serverPort);
+ cl->serverHost = g_strdup(s);
+ g_free(s);
+ /* Support short-form (:0, :1) */
+ if (cl->serverPort < 100)
+ cl->serverPort += VNC_DEFAULT_PORT;
+ }
+ }
+ g_free(host);
+ host = NULL;
+
+ if (cl->serverHost && strstr(cl->serverHost, "unix://") != cl->serverHost && remmina_plugin_service->file_get_string(remminafile, "proxy")) {
+ remmina_plugin_service->get_server_port(
+ remmina_plugin_service->file_get_string(remminafile, "server"),
+ VNC_DEFAULT_PORT,
+ &cl->destHost,
+ &cl->destPort);
+ remmina_plugin_service->get_server_port(
+ remmina_plugin_service->file_get_string(remminafile, "proxy"),
+ VNC_DEFAULT_PORT,
+ &cl->serverHost,
+ &cl->serverPort);
+ REMMINA_PLUGIN_DEBUG("cl->serverHost: %s", cl->serverHost);
+ REMMINA_PLUGIN_DEBUG("cl->serverPort: %d", cl->serverPort);
+ REMMINA_PLUGIN_DEBUG("cl->destHost: %s", cl->destHost);
+ REMMINA_PLUGIN_DEBUG("cl->destPort: %d", cl->destPort);
+ }
+
+ cl->appData.useRemoteCursor = (
+ remmina_plugin_service->file_get_int(remminafile, "showcursor", FALSE) ? FALSE : TRUE);
+
+ remmina_plugin_vnc_update_quality(cl, quality);
+ remmina_plugin_vnc_update_colordepth(cl, colordepth);
+ if ((cl->format.depth == 8) && (quality == 9))
+ cl->appData.encodingsString = "copyrect zlib hextile raw";
+ else if ((cl->format.depth == 8) && (quality == 2))
+ cl->appData.encodingsString = "zrle ultra copyrect hextile zlib corre rre raw";
+ else if ((cl->format.depth == 8) && (quality == 1))
+ cl->appData.encodingsString = "zrle ultra copyrect hextile zlib corre rre raw";
+ else if ((cl->format.depth == 8) && (quality == 0))
+ cl->appData.encodingsString = "zrle ultra copyrect hextile zlib corre rre raw";
+ SetFormatAndEncodings(cl);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "disableencryption", FALSE)) {
+ vnc_encryption_disable_requested = TRUE;
+ SetClientAuthSchemes(cl, remmina_plugin_vnc_no_encrypt_auth_types, -1);
+ } else {
+ vnc_encryption_disable_requested = FALSE;
+ }
+
+ if (rfbInitClient(cl, NULL, NULL)) {
+ REMMINA_PLUGIN_DEBUG("Client initialization successfull");
+ break;
+ } else {
+ REMMINA_PLUGIN_DEBUG("Client initialization failed");
+ }
+
+ /* If the authentication is not called, it has to be a fatal error and must quit */
+ if (!gpdata->auth_called) {
+ REMMINA_PLUGIN_DEBUG("Client not authenticated");
+ gpdata->connected = FALSE;
+ break;
+ }
+
+ /* vnc4server reports "already in use" after authentication. Workaround here */
+ if (strstr(vnc_error, "The server is already in use")) {
+ gpdata->connected = FALSE;
+ gpdata->auth_called = FALSE;
+ break;
+ }
+ /* Don't assume authentication failed for known network-related errors in
+ libvncclient/sockets.c. */
+ if (strstr(vnc_error, "read (") || strstr(vnc_error, "select\n") ||
+ strstr(vnc_error, "write\n") || strstr(vnc_error, "Connection timed out")) {
+ gpdata->connected = FALSE;
+ gpdata->auth_called = FALSE;
+ break;
+ }
+
+ /* Otherwise, it’s a password error. Try to clear saved password if any */
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+
+ if (!gpdata->connected)
+ break;
+
+ remmina_plugin_service->protocol_plugin_init_show_retry(gp);
+
+ /* It’s safer to sleep a while before reconnect */
+ sleep(2);
+
+ gpdata->auth_first = FALSE;
+ }
+
+ if (!gpdata->connected) {
+ REMMINA_PLUGIN_DEBUG("Client not connected with error: %s", vnc_error);
+ if (cl && !gpdata->auth_called && !(remmina_plugin_service->protocol_plugin_has_error(gp)))
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", vnc_error);
+ gpdata->running = FALSE;
+
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+
+ return FALSE;
+ }
+
+ REMMINA_PLUGIN_DEBUG("Client connected");
+ remmina_plugin_service->protocol_plugin_init_save_cred(gp);
+
+ gpdata->client = cl;
+
+ remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "disableserverinput", FALSE))
+ PermitServerInput(cl, 1);
+
+ if (gpdata->thread) {
+ while (remmina_plugin_vnc_main_loop(gp)) {
+ }
+ gpdata->running = FALSE;
+ } else {
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_main_loop, gp);
+ }
+
+ return FALSE;
+}
+
+
+static gpointer
+remmina_plugin_vnc_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ CANCEL_ASYNC
+ remmina_plugin_vnc_main((RemminaProtocolWidget *)data);
+ return NULL;
+}
+
+
+static RemminaPluginVncCoordinates remmina_plugin_vnc_scale_coordinates(GtkWidget *widget, RemminaProtocolWidget *gp, gint x, gint y)
+{
+ GtkAllocation widget_allocation;
+ RemminaPluginVncCoordinates result;
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE)) {
+ gtk_widget_get_allocation(widget, &widget_allocation);
+ result.x = x * remmina_plugin_service->protocol_plugin_get_width(gp) / widget_allocation.width;
+ result.y = y * remmina_plugin_service->protocol_plugin_get_height(gp) / widget_allocation.height;
+ } else {
+ result.x = x;
+ result.y = y;
+ }
+
+ return result;
+}
+
+static gboolean remmina_plugin_vnc_on_motion(GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_on_button(GtkWidget *widget, GdkEventButton *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+ gint mask;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ /* We only accept 3 buttons */
+ if (event->button < 1 || event->button > 3)
+ return FALSE;
+ /* We bypass 2button-press and 3button-press events */
+ if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE)
+ return TRUE;
+
+ mask = (1 << (event->button - 1));
+ gpdata->button_mask = (event->type == GDK_BUTTON_PRESS ? (gpdata->button_mask | mask) :
+ (gpdata->button_mask & (0xff - mask)));
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+ return TRUE;
+}
+
+static gint delta_to_mask(float delta, float *accum, gint mask_plus, gint mask_minus)
+{
+ *accum += delta;
+ if (*accum >= 1.0) {
+ *accum = 0.0;
+ return mask_plus;
+ } else if (*accum <= -1.0) {
+ *accum = 0.0;
+ return mask_minus;
+ }
+ return 0;
+}
+
+static gboolean remmina_plugin_vnc_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+ gint mask;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ mask = (1 << 3);
+ gpdata->scroll_y_accumulator = 0;
+ break;
+ case GDK_SCROLL_DOWN:
+ mask = (1 << 4);
+ gpdata->scroll_y_accumulator = 0;
+ break;
+ case GDK_SCROLL_LEFT:
+ mask = (1 << 5);
+ gpdata->scroll_x_accumulator = 0;
+ break;
+ case GDK_SCROLL_RIGHT:
+ mask = (1 << 6);
+ gpdata->scroll_x_accumulator = 0;
+ break;
+#if GTK_CHECK_VERSION(3, 4, 0)
+ case GDK_SCROLL_SMOOTH:
+ /* RFB does not seems to support SMOOTH scroll, so we accumulate GTK delta requested
+ * up to 1.0 and then send a normal RFB wheel scroll when the accumulator reaches 1.0 */
+ mask = delta_to_mask(event->delta_y, &(gpdata->scroll_y_accumulator), (1 << 4), (1 << 3));
+ mask |= delta_to_mask(event->delta_x, &(gpdata->scroll_x_accumulator), (1 << 6), (1 << 5));
+ if (!mask)
+ return FALSE;
+ break;
+#endif
+ default:
+ return FALSE;
+ }
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(mask | gpdata->button_mask));
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_release_key(RemminaProtocolWidget *gp, guint16 keycode)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaKeyVal *k;
+ gint i;
+
+ if (!gpdata)
+ return;
+
+ if (keycode == 0) {
+ /* Send all release key events for previously pressed keys */
+ for (i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER(k->keyval),
+ GINT_TO_POINTER(FALSE), NULL);
+ g_free(k);
+ }
+ g_ptr_array_set_size(gpdata->pressed_keys, 0);
+ } else {
+ /* Unregister the keycode only */
+ for (i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ if (k->keycode == keycode) {
+ g_free(k);
+ g_ptr_array_remove_index_fast(gpdata->pressed_keys, i);
+ break;
+ }
+ }
+ }
+}
+
+static gboolean remmina_plugin_vnc_on_key(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaKeyVal *k;
+ guint event_keyval;
+ guint keyval;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ gpdata->scroll_x_accumulator = 0;
+ gpdata->scroll_y_accumulator = 0;
+
+ /* When sending key release, try first to find out a previously sent keyval
+ * to workaround bugs like https://bugs.freedesktop.org/show_bug.cgi?id=7430 */
+
+ event_keyval = event->keyval;
+ if (event->type == GDK_KEY_RELEASE) {
+ for (int i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ if (k->keycode == event->hardware_keycode) {
+ event_keyval = k->keyval;
+ break;
+ }
+ }
+ }
+
+ keyval = remmina_plugin_service->pref_keymap_get_keyval(remmina_plugin_service->file_get_string(remminafile, "keymap"),
+ event_keyval);
+
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER(keyval),
+ GINT_TO_POINTER(event->type == GDK_KEY_PRESS ? TRUE : FALSE), NULL);
+
+ /* Register/unregister the pressed key */
+ if (event->type == GDK_KEY_PRESS) {
+ k = g_new(RemminaKeyVal, 1);
+ k->keyval = keyval;
+ k->keycode = event->hardware_keycode;
+ g_ptr_array_add(gpdata->pressed_keys, k);
+ } else {
+ remmina_plugin_vnc_release_key(gp, event->hardware_keycode);
+ }
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_on_cuttext_request(GtkClipboard *clipboard, const gchar *text, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GDateTime *t;
+ glong diff;
+ gsize br, bw;
+ gchar *latin1_text;
+ const char *cur_charset;
+
+ if (text) {
+ /* A timer (1 second) to avoid clipboard "loopback": text cut out from VNC won’t paste back into VNC */
+ t = g_date_time_new_now_utc();
+ diff = g_date_time_difference(t, gpdata->clipboard_timer) / 100000; // tenth of second
+ if (diff < 10)
+ return;
+ g_date_time_unref(gpdata->clipboard_timer);
+ gpdata->clipboard_timer = t;
+ /* Convert text from current charset to latin-1 before sending to remote server.
+ * See RFC6143 7.5.6 */
+ g_get_charset(&cur_charset);
+ latin1_text = g_convert_with_fallback(text, -1, "ISO-8859-1", cur_charset, "?", &br, &bw, NULL);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CUTTEXT, (gpointer)latin1_text, NULL, NULL);
+ g_free(latin1_text);
+ }
+}
+
+static void remmina_plugin_vnc_on_cuttext(GtkClipboard *clipboard, GdkEvent *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ if (!gpdata->connected || !gpdata->client)
+ return;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return;
+
+ gtk_clipboard_request_text(clipboard, (GtkClipboardTextReceivedFunc)remmina_plugin_vnc_on_cuttext_request, gp);
+}
+
+static void remmina_plugin_vnc_on_realize(RemminaProtocolWidget *gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+ GdkCursor *cursor;
+ GdkPixbuf *pixbuf;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "showcursor", FALSE)) {
+ /* Hide local cursor (show a small dot instead) */
+ pixbuf = gdk_pixbuf_new_from_xpm_data(dot_cursor_xpm);
+ cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf, dot_cursor_x_hot, dot_cursor_y_hot);
+ g_object_unref(pixbuf);
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(gp)), cursor);
+ g_object_unref(cursor);
+ }
+}
+
+/******************************************************************************************/
+
+static gboolean remmina_plugin_vnc_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->connected = TRUE;
+ gchar *server;
+ gint port;
+ const gchar* raw_server;
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->drawing_area);
+
+ g_signal_connect(G_OBJECT(gp), "realize", G_CALLBACK(remmina_plugin_vnc_on_realize), NULL);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "motion-notify-event", G_CALLBACK(remmina_plugin_vnc_on_motion), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "button-press-event", G_CALLBACK(remmina_plugin_vnc_on_button), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "button-release-event", G_CALLBACK(remmina_plugin_vnc_on_button), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "scroll-event", G_CALLBACK(remmina_plugin_vnc_on_scroll), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "key-press-event", G_CALLBACK(remmina_plugin_vnc_on_key), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "key-release-event", G_CALLBACK(remmina_plugin_vnc_on_key), gp);
+
+ if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE))
+ gpdata->clipboard_handler = g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
+ "owner-change", G_CALLBACK(remmina_plugin_vnc_on_cuttext), gp);
+
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_vnc_main_thread, gp)) {
+ /* I don’t think this will ever happen… */
+ g_print("Could not initialize pthread. Falling back to non-thread mode…\n");
+ g_timeout_add(0, (GSourceFunc)remmina_plugin_vnc_main, gp);
+ gpdata->thread = 0;
+ }
+
+ raw_server = remmina_plugin_service->file_get_string(remminafile, "server");
+
+ if (raw_server && strstr(raw_server, "unix://") == raw_server) {
+ REMMINA_PLUGIN_AUDIT(_("Connected to %s via VNC"), raw_server);
+ } else {
+ remmina_plugin_service->get_server_port(raw_server,
+ VNC_DEFAULT_PORT,
+ &server,
+ &port);
+
+ REMMINA_PLUGIN_AUDIT(_("Connected to %s:%d via VNC"), server, port);
+ g_free(server), server = NULL;
+ }
+#if LIBVNCSERVER_CHECK_VERSION_VERSION(0, 9, 14)
+ remmina_plugin_service->protocol_plugin_unlock_dynres(gp);
+#endif
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_close_connection_timeout(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gchar *server;
+ gint port;
+
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
+ VNC_DEFAULT_PORT,
+ &server,
+ &port);
+
+ REMMINA_PLUGIN_AUDIT(_("Disconnected from %s:%d via VNC"), server, port);
+ g_free(server), server = NULL;
+
+ /* wait until the running attribute is set to false by the VNC thread */
+ if (gpdata->running)
+ return TRUE;
+
+ /* unregister the clipboard monitor */
+ if (gpdata->clipboard_handler) {
+ g_signal_handler_disconnect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)), gpdata->clipboard_handler);
+ gpdata->clipboard_handler = 0;
+ }
+
+ if (gpdata->queuecursor_handler) {
+ g_source_remove(gpdata->queuecursor_handler);
+ gpdata->queuecursor_handler = 0;
+ }
+ if (gpdata->queuecursor_surface) {
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ gpdata->queuecursor_surface = NULL;
+ }
+
+ if (gpdata->queuedraw_handler) {
+ g_source_remove(gpdata->queuedraw_handler);
+ gpdata->queuedraw_handler = 0;
+ }
+ if (gpdata->listen_sock >= 0)
+ close(gpdata->listen_sock);
+ if (gpdata->client) {
+ rfbClientCleanup((rfbClient *)gpdata->client);
+ gpdata->client = NULL;
+ }
+ if (gpdata->rgb_buffer) {
+ cairo_surface_destroy(gpdata->rgb_buffer);
+ gpdata->rgb_buffer = NULL;
+ }
+ if (gpdata->vnc_buffer) {
+ g_free(gpdata->vnc_buffer);
+ gpdata->vnc_buffer = NULL;
+ }
+ g_ptr_array_free(gpdata->pressed_keys, TRUE);
+ g_date_time_unref(gpdata->clipboard_timer);
+ remmina_plugin_vnc_event_free_all(gp);
+ g_queue_free(gpdata->vnc_event_queue);
+ pthread_mutex_destroy(&gpdata->vnc_event_queue_mutex);
+ close(gpdata->vnc_event_pipe[0]);
+ close(gpdata->vnc_event_pipe[1]);
+
+
+ pthread_mutex_destroy(&gpdata->buffer_mutex);
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->connected = FALSE;
+
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread) pthread_join(gpdata->thread, NULL);
+ gpdata->running = FALSE;
+ remmina_plugin_vnc_close_connection_timeout(gp);
+ } else {
+ g_timeout_add(200, (GSourceFunc)remmina_plugin_vnc_close_connection_timeout, gp);
+ }
+
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
+ return SupportsClient2Server((rfbClient *)(gpdata->client), rfbSetServerInput) ? TRUE : FALSE;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
+ return SupportsClient2Server((rfbClient *)(gpdata->client), rfbTextChat) ? TRUE : FALSE;
+ default:
+ return TRUE;
+ }
+}
+
+static void remmina_plugin_vnc_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ rfbClient* client;
+ uint8_t previous_bpp;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY:
+ remmina_plugin_vnc_update_quality((rfbClient *)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "quality", 9));
+ remmina_plugin_vnc_update_colordepth((rfbClient *)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "colordepth", 32));
+ SetFormatAndEncodings((rfbClient *)(gpdata->client));
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_COLOR:
+ client = (rfbClient *)(gpdata->client);
+ previous_bpp = client->format.bitsPerPixel;
+ remmina_plugin_vnc_update_colordepth(client,
+ remmina_plugin_service->file_get_int(remminafile, "colordepth", 32));
+ SetFormatAndEncodings(client);
+ //Need to clear away old and reallocate if we're increasing bpp
+ if (client->format.bitsPerPixel > previous_bpp){
+ remmina_plugin_vnc_rfb_allocfb((rfbClient *)(gpdata->client));
+ SendFramebufferUpdateRequest((rfbClient *)(gpdata->client), 0, 0,
+ remmina_plugin_service->protocol_plugin_get_width(gp),
+ remmina_plugin_service->protocol_plugin_get_height(gp), FALSE);
+ }
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY:
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
+ PermitServerInput((rfbClient *)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "disableserverinput", FALSE) ? 1 : 0);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS:
+ remmina_plugin_vnc_release_key(gp, 0);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_SCALE:
+ remmina_plugin_vnc_update_scale(gp, remmina_plugin_service->file_get_int(remminafile, "scale", FALSE));
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH:
+ SendFramebufferUpdateRequest((rfbClient *)(gpdata->client), 0, 0,
+ remmina_plugin_service->protocol_plugin_get_width(gp),
+ remmina_plugin_service->protocol_plugin_get_height(gp), FALSE);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
+ remmina_plugin_vnc_open_chat(gp);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_vnc_send_ctrlaltdel(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Send a keystroke to the plugin window */
+static void remmina_plugin_vnc_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->drawing_area,
+ keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE);
+ return;
+}
+
+#if LIBVNCSERVER_CHECK_VERSION_VERSION(0, 9, 14)
+static gboolean remmina_plugin_vnc_on_size_allocate(GtkWidget *widget, GtkAllocation *alloc, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaScaleMode scale_mode = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (scale_mode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES){
+ char str[1024];
+ sprintf(str, "DEBUG: %d x %d", alloc->width, alloc->height);
+ TRACE_CALL(str);
+ if (gpdata->client){
+ SendExtDesktopSize(gpdata->client, alloc->width, alloc->height);
+ }
+ }
+ return TRUE;
+}
+#endif
+
+static gboolean remmina_plugin_vnc_on_draw(GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ cairo_surface_t *surface;
+ gint width, height;
+ GtkAllocation widget_allocation;
+
+ LOCK_BUFFER(FALSE);
+
+ surface = gpdata->rgb_buffer;
+ if (!surface) {
+ UNLOCK_BUFFER(FALSE);
+ return FALSE;
+ }
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE)) {
+ gtk_widget_get_allocation(widget, &widget_allocation);
+ cairo_scale(context,
+ (double)widget_allocation.width / width,
+ (double)widget_allocation.height / height);
+ }
+
+ cairo_rectangle(context, 0, 0, width, height);
+ cairo_set_source_surface(context, surface, 0, 0);
+ cairo_fill(context);
+
+ UNLOCK_BUFFER(FALSE);
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata;
+ gint flags;
+ gdouble aspect_ratio;
+
+ gpdata = g_new0(RemminaPluginVncData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gboolean disable_smooth_scrolling = FALSE;
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ disable_smooth_scrolling = remmina_plugin_service->file_get_int(remminafile, "disablesmoothscrolling", FALSE);
+ REMMINA_PLUGIN_DEBUG("Disable smooth scrolling is set to %d", disable_smooth_scrolling);
+
+ gpdata->drawing_area = gtk_drawing_area_new();
+ gtk_widget_show(gpdata->drawing_area);
+
+ aspect_ratio = remmina_plugin_service->file_get_double(remminafile, "aspect_ratio", 0);
+ if (aspect_ratio > 0){
+ GtkWidget* aspectframe = gtk_aspect_frame_new(NULL, 0, 0, aspect_ratio, FALSE);
+
+ gtk_frame_set_shadow_type(GTK_FRAME(aspectframe), GTK_SHADOW_NONE);
+ gtk_widget_show(aspectframe);
+ gtk_container_add(GTK_CONTAINER(aspectframe), gpdata->drawing_area);
+ gtk_container_add(GTK_CONTAINER(gp), aspectframe);
+ }
+ else{
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->drawing_area);
+ }
+
+ gtk_widget_add_events(
+ gpdata->drawing_area,
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK
+ | GDK_KEY_RELEASE_MASK
+ | GDK_SCROLL_MASK);
+ gtk_widget_set_can_focus(gpdata->drawing_area, TRUE);
+
+ if (!disable_smooth_scrolling) {
+ REMMINA_PLUGIN_DEBUG("Adding GDK_SMOOTH_SCROLL_MASK");
+ gtk_widget_add_events(gpdata->drawing_area, GDK_SMOOTH_SCROLL_MASK);
+ }
+
+
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "draw", G_CALLBACK(remmina_plugin_vnc_on_draw), gp);
+#if LIBVNCSERVER_CHECK_VERSION_VERSION(0, 9, 14)
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "size-allocate", G_CALLBACK(remmina_plugin_vnc_on_size_allocate), gp);
+#endif
+ gpdata->auth_first = TRUE;
+ gpdata->clipboard_timer = g_date_time_new_now_utc();
+ gpdata->listen_sock = -1;
+ gpdata->pressed_keys = g_ptr_array_new();
+ gpdata->vnc_event_queue = g_queue_new();
+ pthread_mutex_init(&gpdata->vnc_event_queue_mutex, NULL);
+ if (pipe(gpdata->vnc_event_pipe)) {
+ g_print("Error creating pipes.\n");
+ gpdata->vnc_event_pipe[0] = 0;
+ gpdata->vnc_event_pipe[1] = 0;
+ }
+ flags = fcntl(gpdata->vnc_event_pipe[0], F_GETFL, 0);
+ fcntl(gpdata->vnc_event_pipe[0], F_SETFL, flags | O_NONBLOCK);
+
+ pthread_mutex_init(&gpdata->buffer_mutex, NULL);
+}
+
+/* Array of key/value pairs for color depths */
+static gpointer colordepth_list[] =
+{
+ "32", N_("True colour (32 bpp)"),
+ "16", N_("High colour (16 bpp)"),
+ "8", N_("256 colours (8 bpp)"),
+ NULL
+};
+
+/* Array of key/value pairs for quality selection */
+static gpointer quality_list[] =
+{
+ "2", N_("Good"),
+ "9", N_("Best (slowest)"),
+ "1", N_("Medium"),
+ "0", N_("Poor (fastest)"),
+ NULL
+};
+
+static gchar repeater_tooltip[] =
+ N_("Connect to VNC using a repeater:\n"
+ " • The server field must contain the repeater ID, e.g. ID:123456789\n"
+ " • The repeater field have to be set to the repeater IP and port, like:\n"
+ " 10.10.10.12:5901\n"
+ " • From the remote VNC server, you will connect to\n"
+ " the repeater, e.g. with x11vnc:\n"
+ " x11vnc -connect repeater=ID:123456789+10.10.10.12:5500");
+
+static gchar vnciport_tooltip[] =
+ N_("Listening for remote VNC connection:\n"
+ " • The “Listen on port” field is the port Remmina will listen to,\n"
+ " e.g. 8888\n"
+ " • From the remote VNC server, you will connect to\n"
+ " Remmina, e.g. with x11vnc:\n"
+ " x11vnc -display :0 -connect 192.168.1.36:8888");
+
+static gchar aspect_ratio_tooltip[] =
+ N_("Lock the aspect ratio when dynamic resolution is enabled:\n"
+ "\n"
+ " • The aspect ratio should be entered as a decimal number, e.g. 1.777\n"
+ " • 16:9 corresponds roughly to 1.7777, 4:3 corresponds roughly to 1.333\n"
+ " • The default value of 0 does not enforce any aspect ratio");
+
+static gchar vncencodings_tooltip[] =
+ N_("Overriding the pre-set VNC encoding quality:\n"
+ "\n"
+ " • “Poor (fastest)” sets encoding to “copyrect zlib hextile raw”\n"
+ " • “Medium” sets encoding to “tight zrle ultra copyrect hextile zlib corre rre raw”\n"
+ " • “Good” sets encoding to “tight zrle ultra copyrect hextile zlib corre rre raw”\n"
+ " • “Best (slowest)” sets encoding to “copyrect zrle ultra zlib hextile corre rre raw”");
+
+/* 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_plugin_vnc_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, "_rfb._tcp", NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "proxy", N_("Repeater"), FALSE, NULL, repeater_tooltip, 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_SELECT, "colordepth", N_("Colour depth"), FALSE, colordepth_list, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, "keymap", NULL, FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL }
+};
+
+// Same as above.
+static const RemminaProtocolSetting remmina_plugin_vnci_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "listenport", N_("Listen on port"), FALSE, NULL, vnciport_tooltip, 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_SELECT, "colordepth", N_("Colour depth"), FALSE, colordepth_list, NULL, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, "keymap", NULL, 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 remmina_plugin_vnc_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "encodings", N_("Override pre-set VNC encodings"), FALSE, NULL, vncencodings_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "aspect_ratio", N_("Dynamic resolution enforced aspec ratio"), FALSE, NULL, aspect_ratio_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "tightencoding", N_("Force tight encoding"), TRUE, NULL, N_("Enabling this may help when the remote desktop looks scrambled") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablesmoothscrolling", N_("Disable smooth scrolling"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverbell", N_("Ignore remote bell messages"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Prevent local interaction on the server"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Turn off clipboard sync"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Turn off encryption"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_plugin_vnc_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_RADIO), "quality",
+ quality_list },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_COLOR, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_RADIO), "colordepth",
+ colordepth_list },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly",
+ N_("View only") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "disableserverinput",N_("Prevent local interaction on the server") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH, N_("Refresh"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT, N_("Open Chat…"), "face-smile", NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_PLUGIN_VNC_FEATURE_SCALE, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS, NULL, NULL, NULL },
+#if LIBVNCSERVER_CHECK_VERSION_VERSION(0, 9, 14)
+ { REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE, REMMINA_PLUGIN_VNC_FEATURE_DYNRESUPDATE, NULL, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_vnc =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ VNC_PLUGIN_NAME, // Name
+ VNC_PLUGIN_DESCRIPTION, // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VNC_PLUGIN_VERSION, // Version number
+ VNC_PLUGIN_APPICON, // Icon for normal connection
+ VNC_PLUGIN_SSH_APPICON, // Icon for SSH connection
+ remmina_plugin_vnc_basic_settings, // Array for basic settings
+ remmina_plugin_vnc_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_vnc_features, // Array for available features
+ remmina_plugin_vnc_init, // Plugin initialization
+ remmina_plugin_vnc_open_connection, // Plugin open connection
+ remmina_plugin_vnc_close_connection, // Plugin close connection
+ remmina_plugin_vnc_query_feature, // Query for available features
+ remmina_plugin_vnc_call_feature, // Call a feature
+ remmina_plugin_vnc_keystroke // Send a keystroke
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_vnci =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ VNCI_PLUGIN_NAME, // Name
+ VNCI_PLUGIN_DESCRIPTION, // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ VNCI_PLUGIN_APPICON, // Icon for normal connection
+ VNCI_PLUGIN_SSH_APPICON, // Icon for SSH connection
+ remmina_plugin_vnci_basic_settings, // Array for basic settings
+ remmina_plugin_vnc_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL, // SSH settings type
+ remmina_plugin_vnc_features, // Array for available features
+ remmina_plugin_vnc_init, // Plugin initialization
+ remmina_plugin_vnc_open_connection, // Plugin open connection
+ remmina_plugin_vnc_close_connection, // Plugin close connection
+ remmina_plugin_vnc_query_feature, // Query for available features
+ remmina_plugin_vnc_call_feature, // Call a feature
+ remmina_plugin_vnc_keystroke, // Send a keystroke
+ NULL, // 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_vnc))
+ return FALSE;
+
+ if (!service->register_plugin((RemminaPlugin *)&remmina_plugin_vnci))
+ return FALSE;
+
+ return TRUE;
+}