/* * 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 #include "vnc_plugin.h" #include #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; }