/* * Remmina - The GTK+ Remote Desktop Client * Copyright (C) 2010 Vic Lee * * 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., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #include "common/remminaplugincommon.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 typedef struct _RemminaPluginVncData { /* Whether the user requests to connect/disconnect */ gboolean connected; /* Whether the vnc process is running */ gboolean running; /* Whether the initialzation calls the authentication process */ gboolean auth_called; /* Whether it is the first attempt for authentication. Only first attempt will try to use cached credentials */ gboolean auth_first; GtkWidget *drawing_area; guchar *vnc_buffer; GdkPixbuf *rgb_buffer; GdkPixbuf *scale_buffer; gint scale_width; gint scale_height; guint scale_handler; gint queuedraw_x, queuedraw_y, queuedraw_w, queuedraw_h; guint queuedraw_handler; gulong clipboard_handler; GTimeVal clipboard_timer; GdkPixbuf *queuecursor_pixbuf; gint queuecursor_x, queuecursor_y; guint queuecursor_handler; gpointer client; gint listen_sock; gint button_mask; GPtrArray *pressed_keys; GQueue *vnc_event_queue; gint vnc_event_pipe[2]; #ifdef HAVE_PTHREAD pthread_t thread; pthread_mutex_t buffer_mutex; #else gint thread; #endif } RemminaPluginVncData; static RemminaPluginService *remmina_plugin_service = NULL; #define dot_cursor_width 5 #define dot_cursor_height 5 #define dot_cursor_x_hot 2 #define dot_cursor_y_hot 2 static const gchar dot_cursor_bits[] = { 0x00, 0x0e, 0x0e, 0x0e, 0x00 }; static const gchar dot_cursor_mask_bits[] = { 0x0e, 0x1f, 0x1f, 0x1f, 0x0e }; #ifdef HAVE_PTHREAD #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} #else #define LOCK_BUFFER(t) #define UNLOCK_BUFFER(t) #endif enum { REMMINA_PLUGIN_VNC_EVENT_KEY, REMMINA_PLUGIN_VNC_EVENT_POINTER, REMMINA_PLUGIN_VNC_EVENT_CUTTEXT, REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN, REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND, REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE }; typedef struct _RemminaPluginVncEvent { gint event_type; union { struct { guint keyval; gboolean pressed; } key; struct { gint x; gint y; gint button_mask; } pointer; struct { gchar *text; } text; } event_data; } RemminaPluginVncEvent; static void remmina_plugin_vnc_event_push (RemminaProtocolWidget *gp, gint event_type, gpointer p1, gpointer p2, gpointer p3) { RemminaPluginVncData *gpdata; RemminaPluginVncEvent *event; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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; } g_queue_push_tail (gpdata->vnc_event_queue, event); if (write (gpdata->vnc_event_pipe[1], "\0", 1)) { /* Ignore */ } } static void remmina_plugin_vnc_event_free (RemminaPluginVncEvent *event) { 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) { RemminaPluginVncData *gpdata; RemminaPluginVncEvent *event; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaPluginVncData *gpdata; gint sx, sy, sw, sh; gint width, height; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (gpdata->rgb_buffer == NULL || gpdata->scale_buffer == NULL) return; width = remmina_plugin_service->protocol_plugin_get_width (gp); height = remmina_plugin_service->protocol_plugin_get_height (gp); if (gpdata->scale_width == width && gpdata->scale_height == height) { /* Same size, just copy the pixels */ gdk_pixbuf_copy_area (gpdata->rgb_buffer, *x, *y, *w, *h, gpdata->scale_buffer, *x, *y); return; } /* We have to extend the scaled region 2 scaled pixels, to avoid gaps */ sx = MIN (MAX (0, (*x) * gpdata->scale_width / width - gpdata->scale_width / width - 2), gpdata->scale_width - 1); sy = MIN (MAX (0, (*y) * gpdata->scale_height / height - gpdata->scale_height / height - 2), gpdata->scale_height - 1); sw = MIN (gpdata->scale_width - sx, (*w) * gpdata->scale_width / width + gpdata->scale_width / width + 4); sh = MIN (gpdata->scale_height - sy, (*h) * gpdata->scale_height / height + gpdata->scale_height / height + 4); gdk_pixbuf_scale (gpdata->rgb_buffer, gpdata->scale_buffer, sx, sy, sw, sh, 0, 0, (double) gpdata->scale_width / (double) width, (double) gpdata->scale_height / (double) height, remmina_plugin_service->pref_get_scale_quality ()); *x = sx; *y = sy; *w = sw; *h = sh; } static gboolean remmina_plugin_vnc_update_scale_buffer (RemminaProtocolWidget *gp, gboolean in_thread) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint width, height; gint gpwidth, gpheight; gint hscale, vscale; gboolean scale; gint x, y, w, h; GdkPixbuf *pixbuf; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); if (gpdata->running) { width = GTK_WIDGET (gp)->allocation.width; height = GTK_WIDGET (gp)->allocation.height; scale = remmina_plugin_service->protocol_plugin_get_scale (gp); if (scale) { if (width > 1 && height > 1) { LOCK_BUFFER (in_thread) if (gpdata->scale_buffer) { g_object_unref (gpdata->scale_buffer); } gpwidth = remmina_plugin_service->protocol_plugin_get_width (gp); gpheight = remmina_plugin_service->protocol_plugin_get_height (gp); hscale = remmina_plugin_service->file_get_int (remminafile, "hscale", 0); vscale = remmina_plugin_service->file_get_int (remminafile, "vscale", 0); gpdata->scale_width = (hscale > 0 ? MAX (1, gpwidth * hscale / 100) : width); gpdata->scale_height = (vscale > 0 ? MAX (1, gpheight * vscale / 100) : height); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, gpdata->scale_width, gpdata->scale_height); gpdata->scale_buffer = pixbuf; x = 0; y = 0; w = gpwidth; h = gpheight; remmina_plugin_vnc_scale_area (gp, &x, &y, &w, &h); UNLOCK_BUFFER (in_thread) } } else { LOCK_BUFFER (in_thread) if (gpdata->scale_buffer) { g_object_unref (gpdata->scale_buffer); gpdata->scale_buffer = NULL; } gpdata->scale_width = 0; gpdata->scale_height = 0; UNLOCK_BUFFER (in_thread) } if (width > 1 && height > 1) { if (in_thread) { THREADS_ENTER gtk_widget_queue_draw_area (GTK_WIDGET (gp), 0, 0, width, height); THREADS_LEAVE } else { gtk_widget_queue_draw_area (GTK_WIDGET (gp), 0, 0, width, height); } } } gpdata->scale_handler = 0; return FALSE; } static gboolean remmina_plugin_vnc_update_scale_buffer_main (RemminaProtocolWidget *gp) { return remmina_plugin_vnc_update_scale_buffer (gp, FALSE); } static void remmina_plugin_vnc_update_scale (RemminaProtocolWidget *gp, gboolean scale) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint width, height; gint hscale, vscale; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); width = remmina_plugin_service->protocol_plugin_get_width (gp); height = remmina_plugin_service->protocol_plugin_get_height (gp); if (scale) { hscale = remmina_plugin_service->file_get_int (remminafile, "hscale", 0); vscale = remmina_plugin_service->file_get_int (remminafile, "vscale", 0); gtk_widget_set_size_request (GTK_WIDGET (gpdata->drawing_area), (hscale > 0 ? width * hscale / 100 : -1), (vscale > 0 ? height * vscale / 100 : -1)); } else { gtk_widget_set_size_request (GTK_WIDGET (gpdata->drawing_area), width, height); } } gboolean remmina_plugin_vnc_setcursor (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; GdkCursor *cur; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); LOCK_BUFFER (FALSE) gpdata->queuecursor_handler = 0; if (gpdata->queuecursor_pixbuf) { cur = gdk_cursor_new_from_pixbuf (gdk_display_get_default (), gpdata->queuecursor_pixbuf, gpdata->queuecursor_x, gpdata->queuecursor_y); gdk_window_set_cursor (gtk_widget_get_window (gpdata->drawing_area), cur); gdk_cursor_unref (cur); g_object_unref (gpdata->queuecursor_pixbuf); gpdata->queuecursor_pixbuf = 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, GdkPixbuf *pixbuf, gint x, gint y) { RemminaPluginVncData *gpdata; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (gpdata->queuecursor_pixbuf) { g_object_unref (gpdata->queuecursor_pixbuf); } gpdata->queuecursor_pixbuf = pixbuf; 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 *********************************/ #include #ifdef LIBVNCSERVER_WITH_CLIENT_TLS static const uint32_t remmina_plugin_vnc_no_encrypt_auth_types[] = {rfbNoAuth, rfbVncAuth, rfbMSLogon, 0}; #endif static void remmina_plugin_vnc_process_vnc_event (RemminaProtocolWidget *gp) { RemminaPluginVncEvent *event; RemminaPluginVncData *gpdata; rfbClient *cl; gchar buf[100]; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); cl = (rfbClient*) gpdata->client; while ((event = g_queue_pop_head (gpdata->vnc_event_queue)) != 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: 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; } } 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) { switch (quality) { case 9: cl->appData.useBGR233 = 0; cl->appData.encodingsString = "copyrect hextile raw"; cl->appData.compressLevel = 0; 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 = 3; 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 = 5; cl->appData.qualityLevel = 5; break; case 0: default: cl->appData.useBGR233 = 1; cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw"; cl->appData.compressLevel = 9; cl->appData.qualityLevel = 0; break; } } static void remmina_plugin_vnc_update_colordepth (rfbClient *cl, gint colordepth) { cl->format.depth = colordepth; cl->format.bigEndian = 0; cl->appData.requestedDepth = colordepth; switch (colordepth) { case 24: cl->format.bitsPerPixel = 32; cl->format.redMax = 0xff; cl->format.greenMax = 0xff; cl->format.blueMax = 0xff; cl->format.redShift = 16; cl->format.greenShift = 8; cl->format.blueShift = 0; break; case 16: cl->format.bitsPerPixel = 16; cl->format.redMax = 0x1f; cl->format.greenMax = 0x3f; cl->format.blueMax = 0x1f; cl->format.redShift = 11; cl->format.greenShift = 5; cl->format.blueShift = 0; break; case 15: cl->format.bitsPerPixel = 16; cl->format.redMax = 0x1f; cl->format.greenMax = 0x1f; cl->format.blueMax = 0x1f; cl->format.redShift = 10; cl->format.greenShift = 5; cl->format.blueShift = 0; break; case 8: default: cl->format.bitsPerPixel = 8; cl->format.redMax = 7; cl->format.greenMax = 7; cl->format.blueMax = 3; cl->format.redShift = 0; cl->format.greenShift = 3; cl->format.blueShift = 6; break; } } static rfbBool remmina_plugin_vnc_rfb_allocfb (rfbClient *cl) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; gint width, height, depth, size; gboolean scale; GdkPixbuf *new_pixbuf, *old_pixbuf; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); width = cl->width; height = cl->height; depth = cl->format.bitsPerPixel; size = width * height * (depth / 8); /* Putting gdk_pixbuf_new inside a gdk_thread_enter/leave pair could cause dead-lock! */ new_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height); if (new_pixbuf == NULL) return FALSE; gdk_pixbuf_fill (new_pixbuf, 0); old_pixbuf = gpdata->rgb_buffer; LOCK_BUFFER (TRUE) remmina_plugin_service->protocol_plugin_set_width (gp, cl->width); remmina_plugin_service->protocol_plugin_set_height (gp, cl->height); gpdata->rgb_buffer = new_pixbuf; 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_pixbuf) g_object_unref (old_pixbuf); scale = remmina_plugin_service->protocol_plugin_get_scale (gp); THREADS_ENTER remmina_plugin_vnc_update_scale (gp, scale); THREADS_LEAVE if (gpdata->scale_handler == 0) remmina_plugin_vnc_update_scale_buffer (gp, TRUE); /* Notify window of change so that scroll border can be hidden or shown if needed */ remmina_plugin_service->protocol_plugin_emit_signal (gp, "desktop-resize"); /* 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) { gint b = 0; while (n) { b++; n >>= 1; } return b ? b : 1; } static gboolean remmina_plugin_vnc_queue_draw_area_real (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; gint x, y, w, h; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaPluginVncData *gpdata; gint nx2, ny2, ox2, oy2; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { guchar *destptr, *srcptr; gint bytesPerPixel; guint32 pixel; gint ix, iy; gint i; guchar c; gint rs, gs, bs, rm, gm, bm, rl, gl, bl, rr, gr, br; gint r; 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 = dest + iy * dest_rowstride; srcptr = src + iy * src_rowstride; for (ix = 0; ix < w; ix++) { *destptr++ = *(srcptr + 2); *destptr++ = *(srcptr + 1); *destptr++ = *srcptr; if (mask) *destptr++ = (*mask++) ? 0xff : 0x00; 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 = dest + iy * dest_rowstride; srcptr = src + iy * src_rowstride; for (ix = 0; ix < w; ix++) { pixel = 0; for (i = 0; i < bytesPerPixel; i++) pixel += (*srcptr++) << (8 * i); c = (guchar) ((pixel >> rs) & rm) << rl; for (r = rr; r < 8; r *= 2) c |= c >> r; *destptr++ = c; c = (guchar) ((pixel >> gs) & gm) << gl; for (r = gr; r < 8; r *= 2) c |= c >> r; *destptr++ = c; c = (guchar) ((pixel >> bs) & bm) << bl; for (r = br; r < 8; r *= 2) c |= c >> r; *destptr++ = c; if (mask) *destptr++ = (*mask++) ? 0xff : 0x00; } } break; } } static void remmina_plugin_vnc_rfb_updatefb (rfbClient* cl, int x, int y, int w, int h) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; gint bytesPerPixel; gint rowstride; gint width; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); LOCK_BUFFER (TRUE) if (w >= 1 || h >= 1) { width = remmina_plugin_service->protocol_plugin_get_width (gp); bytesPerPixel = cl->format.bitsPerPixel / 8; rowstride = gdk_pixbuf_get_rowstride (gpdata->rgb_buffer); remmina_plugin_vnc_rfb_fill_buffer (cl, gdk_pixbuf_get_pixels (gpdata->rgb_buffer) + y * rowstride + x * 3, rowstride, gpdata->vnc_buffer + ((y * width + x) * bytesPerPixel), width * bytesPerPixel, NULL, w, h); } if (remmina_plugin_service->protocol_plugin_get_scale (gp)) { 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 gboolean remmina_plugin_vnc_queue_cuttext (RemminaPluginVncCuttextParam *param) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; GTimeVal t; glong diff; gp = param->gp; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (GTK_IS_WIDGET (gp) && gpdata->connected) { g_get_current_time (&t); diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10 + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000; if (diff >= 10) { gpdata->clipboard_timer = t; gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), param->text, param->textlen); } } g_free (param->text); g_free (param); return FALSE; } static void remmina_plugin_vnc_rfb_cuttext (rfbClient* cl, const char *text, int textlen) { 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) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint ret; gchar *pwd = NULL; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); gpdata->auth_called = TRUE; remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); if (gpdata->auth_first && remmina_plugin_service->file_get_string (remminafile, "password")) { pwd = g_strdup (remmina_plugin_service->file_get_string (remminafile, "password")); } else { THREADS_ENTER ret = remmina_plugin_service->protocol_plugin_init_authpwd (gp, REMMINA_AUTHPWD_TYPE_PROTOCOL); THREADS_LEAVE if (ret == GTK_RESPONSE_OK) { pwd = remmina_plugin_service->protocol_plugin_init_get_password (gp); } else { gpdata->connected = FALSE; } } return pwd; } #ifdef LIBVNCSERVER_WITH_CLIENT_TLS static rfbCredential* remmina_plugin_vnc_rfb_credential (rfbClient *cl, int credentialType) { rfbCredential *cred; RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint ret; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); gpdata->auth_called = TRUE; remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); cred = g_new0 (rfbCredential, 1); switch (credentialType) { case rfbCredentialTypeUser: if (gpdata->auth_first && remmina_plugin_service->file_get_string (remminafile, "username") && remmina_plugin_service->file_get_string (remminafile, "password")) { cred->userCredential.username = g_strdup (remmina_plugin_service->file_get_string (remminafile, "username")); cred->userCredential.password = g_strdup (remmina_plugin_service->file_get_string (remminafile, "password")); } else { THREADS_ENTER ret = remmina_plugin_service->protocol_plugin_init_authuserpwd (gp); THREADS_LEAVE if (ret == GTK_RESPONSE_OK) { cred->userCredential.username = remmina_plugin_service->protocol_plugin_init_get_username (gp); cred->userCredential.password = remmina_plugin_service->protocol_plugin_init_get_password (gp); } 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 { THREADS_ENTER ret = remmina_plugin_service->protocol_plugin_init_authx509 (gp); THREADS_LEAVE 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; } #endif static void remmina_plugin_vnc_rfb_cursor_shape (rfbClient *cl, int xhot, int yhot, int width, int height, int bytesPerPixel) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; guchar *pixbuf_data; GdkPixbuf *pixbuf; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); if (!GTK_WIDGET (gp)->window) return; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (width && height) { pixbuf_data = g_malloc (width * height * 4); remmina_plugin_vnc_rfb_fill_buffer (cl, pixbuf_data, width * 4, cl->rcSource, width * cl->format.bitsPerPixel / 8, cl->rcMask, width, height); pixbuf = gdk_pixbuf_new_from_data (pixbuf_data, GDK_COLORSPACE_RGB, TRUE, 8, width, height, width * 4, (GdkPixbufDestroyNotify) g_free, NULL); LOCK_BUFFER (TRUE) remmina_plugin_vnc_queuecursor (gp, pixbuf, xhot, yhot); UNLOCK_BUFFER (TRUE) } } static void remmina_plugin_vnc_rfb_bell (rfbClient *cl) { RemminaProtocolWidget *gp; GdkWindow *window; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); window = GTK_WIDGET (gp)->window; if (window) gdk_window_beep (window); } /* 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 void remmina_plugin_vnc_rfb_output(const char *format, ...) { va_list args; va_start(args, format); gchar *f, *p; /* 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 { g_vsnprintf (vnc_error, MAX_ERROR_LENGTH, _(f), args); } g_free (f); va_end(args); remmina_plugin_service->log_printf ("[VNC]%s\n", vnc_error); } static void remmina_plugin_vnc_chat_on_send (RemminaProtocolWidget *gp, const gchar *text) { 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) { remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE, NULL, NULL, NULL); } static gboolean remmina_plugin_vnc_close_chat (RemminaProtocolWidget *gp) { remmina_plugin_service->protocol_plugin_chat_close (gp); return FALSE; } static gboolean remmina_plugin_vnc_open_chat (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; rfbClient *cl; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaProtocolWidget *gp; RemminaPluginVncData *gpdata; gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL)); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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 */ THREADS_ENTER remmina_plugin_service->protocol_plugin_chat_receive (gp, text); THREADS_LEAVE break; } } static gboolean remmina_plugin_vnc_incoming_connection (RemminaProtocolWidget *gp, rfbClient *cl) { RemminaPluginVncData *gpdata; fd_set fds; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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); 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; } cl->sock = AcceptTcpConnection (gpdata->listen_sock); 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) { RemminaPluginVncData *gpdata; gint ret; rfbClient *cl; fd_set fds; struct timeval timeout; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (!gpdata->connected) { gpdata->running = FALSE; return FALSE; } cl = (rfbClient*) gpdata->client; 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)) { ret = HandleRFBServerMessage (cl); if (!ret) { gpdata->running = FALSE; if (gpdata->connected && !remmina_plugin_service->protocol_plugin_is_closed (gp)) { IDLE_ADD ((GSourceFunc) remmina_plugin_service->protocol_plugin_close_connection, gp); } return FALSE; } } return TRUE; } static gboolean remmina_plugin_vnc_main (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; rfbClient *cl = NULL; gchar *host; gchar *s = NULL; remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); gpdata->running = TRUE; rfbClientLog = remmina_plugin_vnc_rfb_output; rfbClientErr = remmina_plugin_vnc_rfb_output; while (gpdata->connected) { gpdata->auth_called = FALSE; host = remmina_plugin_service->protocol_plugin_start_direct_tunnel (gp, 5900, TRUE); if (host == NULL) { gpdata->connected = FALSE; break; } cl = rfbGetClient(8, 3, 4); cl->MallocFrameBuffer = remmina_plugin_vnc_rfb_allocfb; cl->canHandleNewFBSize = TRUE; cl->GetPassword = remmina_plugin_vnc_rfb_password; #ifdef LIBVNCSERVER_WITH_CLIENT_TLS cl->GetCredential = remmina_plugin_vnc_rfb_credential; #endif cl->GotFrameBufferUpdate = remmina_plugin_vnc_rfb_updatefb; 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; rfbClientSetClientData (cl, NULL, gp); if (host[0] == '\0') { cl->serverHost = strdup (host); cl->listenSpecified = TRUE; if (remmina_plugin_service->file_get_int (remminafile, "ssh_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 { remmina_plugin_service->get_server_port (host, 5900, &s, &cl->serverPort); cl->serverHost = strdup (s); g_free (s); /* Support short-form (:0, :1) */ if (cl->serverPort < 100) cl->serverPort += 5900; } g_free (host); host = NULL; if (remmina_plugin_service->file_get_string (remminafile, "proxy")) { #ifdef LIBVNCSERVER_WITH_CLIENT_TLS cl->destHost = cl->serverHost; cl->destPort = cl->serverPort; remmina_plugin_service->get_server_port (remmina_plugin_service->file_get_string (remminafile, "proxy"), 5900, &s, &cl->serverPort); cl->serverHost = strdup (s); g_free (s); #endif } cl->appData.useRemoteCursor = (remmina_plugin_service->file_get_int (remminafile, "showcursor", FALSE) ? FALSE : TRUE); remmina_plugin_vnc_update_quality (cl, remmina_plugin_service->file_get_int (remminafile, "quality", 0)); remmina_plugin_vnc_update_colordepth (cl, remmina_plugin_service->file_get_int (remminafile, "colordepth", 8)); SetFormatAndEncodings (cl); if (remmina_plugin_service->file_get_int (remminafile, "disableencryption", FALSE)) { #ifdef LIBVNCSERVER_WITH_CLIENT_TLS SetClientAuthSchemes (cl, remmina_plugin_vnc_no_encrypt_auth_types, -1); #endif } if (rfbInitClient (cl, NULL, NULL)) break; /* If the authentication is not called, it has to be a fatel error and must quit */ if (!gpdata->auth_called) { 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; } /* 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; THREADS_ENTER remmina_plugin_service->protocol_plugin_init_show_retry (gp); THREADS_LEAVE /* It's safer to sleep a while before reconnect */ sleep (2); gpdata->auth_first = FALSE; } if (!gpdata->connected) { 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; IDLE_ADD ((GSourceFunc) remmina_plugin_service->protocol_plugin_close_connection, gp); return FALSE; } remmina_plugin_service->protocol_plugin_init_save_cred (gp); gpdata->client = cl; remmina_plugin_service->protocol_plugin_emit_signal (gp, "connect"); 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; } #ifdef HAVE_PTHREAD static gpointer remmina_plugin_vnc_main_thread (gpointer data) { pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); CANCEL_ASYNC remmina_plugin_vnc_main ((RemminaProtocolWidget*) data); return NULL; } #endif static gboolean remmina_plugin_vnc_on_motion (GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint x, y; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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; if (remmina_plugin_service->protocol_plugin_get_scale (gp)) { x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width; y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height; } else { x = event->x; y = event->y; } remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask)); return TRUE; } static gboolean remmina_plugin_vnc_on_button (GtkWidget *widget, GdkEventButton *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint x, y; gint mask; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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))); if (remmina_plugin_service->protocol_plugin_get_scale (gp)) { x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width; y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height; } else { x = event->x; y = event->y; } remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask)); return TRUE; } static gboolean remmina_plugin_vnc_on_scroll (GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gint x, y; gint mask; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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); break; case GDK_SCROLL_DOWN: mask = (1 << 4); break; case GDK_SCROLL_LEFT: mask = (1 << 5); break; case GDK_SCROLL_RIGHT: mask = (1 << 6); break; default: return FALSE; } if (remmina_plugin_service->protocol_plugin_get_scale (gp)) { x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width; y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height; } else { x = event->x; y = event->y; } remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (mask | gpdata->button_mask)); remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask)); return TRUE; } static void remmina_plugin_vnc_release_key (RemminaProtocolWidget *gp, guint16 keycode) { RemminaPluginVncData *gpdata; RemminaKeyVal *k; gint i; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; RemminaKeyVal *k; guint keyval; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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; keyval = remmina_plugin_service->pref_keymap_get_keyval ( remmina_plugin_service->file_get_string (remminafile, "gkeymap"), 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) { RemminaPluginVncData *gpdata; GTimeVal t; glong diff; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); if (text) { /* A timer (1 second) to avoid clipboard "loopback": text cut out from VNC won't paste back into VNC */ g_get_current_time (&t); diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10 + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000; if (diff < 10) return; gpdata->clipboard_timer = t; remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CUTTEXT, (gpointer) text, NULL, NULL); } } static void remmina_plugin_vnc_on_cuttext (GtkClipboard *clipboard, GdkEvent *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; GdkCursor *cursor; GdkPixmap *source, *mask; GdkColor fg, bg; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) */ gdk_color_parse ("black", &fg); gdk_color_parse ("white", &bg); source = gdk_bitmap_create_from_data (NULL, dot_cursor_bits, dot_cursor_width, dot_cursor_height); mask = gdk_bitmap_create_from_data (NULL, dot_cursor_mask_bits, dot_cursor_width, dot_cursor_height); cursor = gdk_cursor_new_from_pixmap (source, mask, &fg, &bg, dot_cursor_x_hot, dot_cursor_y_hot); gdk_pixmap_unref (source); gdk_pixmap_unref (mask); gdk_window_set_cursor (GTK_WIDGET (gp)->window, cursor); gdk_cursor_unref (cursor); } } /******************************************************************************************/ static gboolean remmina_plugin_vnc_open_connection (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); remminafile = remmina_plugin_service->protocol_plugin_get_file (gp); gpdata->connected = TRUE; 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); } #ifdef HAVE_PTHREAD if (pthread_create (&gpdata->thread, NULL, remmina_plugin_vnc_main_thread, gp)) { /* I don't think this will ever happen... */ g_print ("Failed to initialize pthread. Falling back to non-thread mode...\n"); g_timeout_add (0, (GSourceFunc) remmina_plugin_vnc_main, gp); gpdata->thread = 0; } #else g_timeout_add (0, (GSourceFunc) remmina_plugin_vnc_main, gp); #endif return TRUE; } static gboolean remmina_plugin_vnc_close_connection_timeout (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); /* 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_pixbuf) { g_object_unref (gpdata->queuecursor_pixbuf); gpdata->queuecursor_pixbuf = NULL; } if (gpdata->queuedraw_handler) { g_source_remove (gpdata->queuedraw_handler); gpdata->queuedraw_handler = 0; } if (gpdata->scale_handler) { g_source_remove (gpdata->scale_handler); gpdata->scale_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) { g_object_unref (gpdata->rgb_buffer); gpdata->rgb_buffer = NULL; } if (gpdata->vnc_buffer) { g_free (gpdata->vnc_buffer); gpdata->vnc_buffer = NULL; } if (gpdata->scale_buffer) { g_object_unref (gpdata->scale_buffer); gpdata->scale_buffer = NULL; } g_ptr_array_free (gpdata->pressed_keys, TRUE); remmina_plugin_vnc_event_free_all (gp); g_queue_free (gpdata->vnc_event_queue); close (gpdata->vnc_event_pipe[0]); close (gpdata->vnc_event_pipe[1]); #ifdef HAVE_PTHREAD pthread_mutex_destroy (&gpdata->buffer_mutex); #endif remmina_plugin_service->protocol_plugin_emit_signal (gp, "disconnect"); return FALSE; } static gboolean remmina_plugin_vnc_close_connection (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); gpdata->connected = FALSE; #ifdef HAVE_PTHREAD 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); } #else g_timeout_add (200, (GSourceFunc) remmina_plugin_vnc_close_connection_timeout, gp); #endif return FALSE; } static gboolean remmina_plugin_vnc_query_feature (RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature) { RemminaPluginVncData *gpdata; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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) { RemminaPluginVncData *gpdata; RemminaFile *remminafile; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); 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", 0)); SetFormatAndEncodings ((rfbClient*) (gpdata->client)); 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; default: break; } } static gboolean remmina_plugin_vnc_on_expose (GtkWidget *widget, GdkEventExpose *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; GdkPixbuf *buffer; gint width, height, x, y, rowstride; gboolean scale; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); LOCK_BUFFER (FALSE) scale = remmina_plugin_service->protocol_plugin_get_scale (gp); /* widget == gpdata->drawing_area */ buffer = (scale ? gpdata->scale_buffer : gpdata->rgb_buffer); if (!buffer) { UNLOCK_BUFFER (FALSE) return FALSE; } width = (scale ? gpdata->scale_width : remmina_plugin_service->protocol_plugin_get_width (gp)); height = (scale ? gpdata->scale_height : remmina_plugin_service->protocol_plugin_get_height (gp)); if (event->area.x >= width || event->area.y >= height) { UNLOCK_BUFFER (FALSE) return FALSE; } x = event->area.x; y = event->area.y; rowstride = gdk_pixbuf_get_rowstride (buffer); /* this is a little tricky. It "moves" the rgb_buffer pointer to (x,y) as top-left corner, and keeps the same rowstride. This is an effective way to "clip" the rgb_buffer for gdk. */ gdk_draw_rgb_image (widget->window, widget->style->white_gc, x, y, MIN (width - x, event->area.width), MIN (height - y, event->area.height), GDK_RGB_DITHER_MAX, gdk_pixbuf_get_pixels (buffer) + y * rowstride + x * 3, rowstride); UNLOCK_BUFFER (FALSE) return TRUE; } static gboolean remmina_plugin_vnc_on_configure (GtkWidget *widget, GdkEventConfigure *event, RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data"); /* We do a delayed reallocating to improve performance */ if (gpdata->scale_handler) g_source_remove (gpdata->scale_handler); gpdata->scale_handler = g_timeout_add (1000, (GSourceFunc) remmina_plugin_vnc_update_scale_buffer_main, gp); return FALSE; } static void remmina_plugin_vnc_init (RemminaProtocolWidget *gp) { RemminaPluginVncData *gpdata; gint flags; gpdata = g_new0 (RemminaPluginVncData, 1); g_object_set_data_full (G_OBJECT (gp), "plugin-data", gpdata, g_free); gpdata->drawing_area = gtk_drawing_area_new (); gtk_widget_show (gpdata->drawing_area); 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); GTK_WIDGET_SET_FLAGS (gpdata->drawing_area, GTK_CAN_FOCUS); g_signal_connect (G_OBJECT (gpdata->drawing_area), "expose_event", G_CALLBACK (remmina_plugin_vnc_on_expose), gp); g_signal_connect (G_OBJECT (gpdata->drawing_area), "configure_event", G_CALLBACK (remmina_plugin_vnc_on_configure), gp); gpdata->auth_first = TRUE; g_get_current_time (&gpdata->clipboard_timer); gpdata->listen_sock = -1; gpdata->pressed_keys = g_ptr_array_new (); gpdata->vnc_event_queue = g_queue_new (); 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); #ifdef HAVE_PTHREAD pthread_mutex_init (&gpdata->buffer_mutex, NULL); #endif } static gpointer colordepth_list[] = { "8", N_("256 colors"), "15", N_("High color (15 bit)"), "16", N_("High color (16 bit)"), "24", N_("True color (24 bit)"), NULL }; static gpointer quality_list[] = { "0", N_("Poor (fastest)"), "1", N_("Medium"), "2", N_("Good"), "9", N_("Best (slowest)"), NULL }; static const RemminaProtocolSetting remmina_plugin_vnc_basic_settings[] = { { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, NULL, NULL, FALSE, "_rfb._tcp", NULL }, #ifdef LIBVNCSERVER_WITH_CLIENT_TLS { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "proxy", N_("Repeater"), FALSE, NULL, NULL }, #endif { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, NULL, NULL, FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), TRUE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Disable server input"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL } }; static const RemminaProtocolSetting remmina_plugin_vnci_basic_settings[] = { { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "listenport", N_("Listen on port"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, NULL, NULL, FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), TRUE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Disable server input"), FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL } }; static const RemminaProtocolSetting remmina_plugin_vnc_advanced_settings[] = { { REMMINA_PROTOCOL_SETTING_TYPE_SCALE, NULL, NULL, FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, NULL, NULL, FALSE, NULL, NULL }, { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL } }; 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_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_("Disable server input") }, { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH, N_("Refresh"), GTK_STOCK_REFRESH, NULL }, { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT, N_("Open Chat..."), "face-smile", 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 }, { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL } }; static RemminaProtocolPlugin remmina_plugin_vnc = { REMMINA_PLUGIN_TYPE_PROTOCOL, "VNC", N_("VNC - Virtual Network Computing"), GETTEXT_PACKAGE, VERSION, "remmina-vnc", "remmina-vnc-ssh", remmina_plugin_vnc_basic_settings, remmina_plugin_vnc_advanced_settings, REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, remmina_plugin_vnc_features, remmina_plugin_vnc_init, remmina_plugin_vnc_open_connection, remmina_plugin_vnc_close_connection, remmina_plugin_vnc_query_feature, remmina_plugin_vnc_call_feature }; static RemminaProtocolPlugin remmina_plugin_vnci = { REMMINA_PLUGIN_TYPE_PROTOCOL, "VNCI", N_("VNC - Incoming Connection"), GETTEXT_PACKAGE, VERSION, "remmina-vnc", "remmina-vnc-ssh", remmina_plugin_vnci_basic_settings, remmina_plugin_vnc_advanced_settings, REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL, remmina_plugin_vnc_features, remmina_plugin_vnc_init, remmina_plugin_vnc_open_connection, remmina_plugin_vnc_close_connection, remmina_plugin_vnc_query_feature, remmina_plugin_vnc_call_feature }; G_MODULE_EXPORT gboolean remmina_plugin_entry (RemminaPluginService *service) { remmina_plugin_service = service; bindtextdomain (GETTEXT_PACKAGE, REMMINA_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; }