Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1989 lines (1719 sloc) 58.7 KB
/*
* Remmina - The GTK+ Remote Desktop Client
* Copyright (C) 2010-2011 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/remmina_plugin.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;
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", " ... ", ".+++.", ".+ +.", ".+++.", " ... " };
#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;
GtkAllocation a;
gpdata = (RemminaPluginVncData*) g_object_get_data(G_OBJECT(gp), "plugin-data");
remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
if (gpdata->running)
{
gtk_widget_get_allocation(GTK_WIDGET(gp), &a);
width = a.width;
height = a.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);
}
remmina_plugin_service->protocol_plugin_emit_signal(gp, "update-align");
}
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);
g_object_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 <rfb/rfbclient.h>
#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)
{
THREADS_ENTER pwd = remmina_plugin_service->file_get_secret(remminafile, "password");
THREADS_LEAVE
}
if (!pwd)
{
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;
gchar *s1, *s2;
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:
s1 = g_strdup (remmina_plugin_service->file_get_string (remminafile, "username"));
THREADS_ENTER
s2 = remmina_plugin_service->file_get_secret (remminafile, "password");
THREADS_LEAVE
if (gpdata->auth_first && s1 && s2)
{
cred->userCredential.username = s1;
cred->userCredential.password = s2;
}
else
{
g_free(s1);
g_free(s2);
THREADS_ENTER
ret = remmina_plugin_service->protocol_plugin_init_authuserpwd (gp, FALSE);
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_get_window(GTK_WIDGET(gp)))
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_get_window(GTK_WIDGET(gp));
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;
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 */
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;
}
THREADS_ENTER
remmina_plugin_service->protocol_plugin_init_save_cred(gp);
THREADS_LEAVE
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;
#ifdef GDK_SCROLL_SMOOTH
case GDK_SCROLL_SMOOTH:
if (event->delta_y < 0)
mask = (1 << 3);
if (event->delta_y > 0)
mask = (1 << 4);
if (event->delta_x < 0)
mask = (1 << 5);
if (event->delta_x > 0)
mask = (1 << 6);
if (!mask)
return FALSE;
break;
#endif
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)
{
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)
{
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;
}
}
#if GTK_VERSION == 2
static gboolean remmina_plugin_vnc_on_expose(GtkWidget *widget, GdkEventExpose *event, RemminaProtocolWidget *gp)
#else
static gboolean remmina_plugin_vnc_on_draw(GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp)
#endif
{
RemminaPluginVncData *gpdata;
GdkPixbuf *buffer;
gboolean scale;
#if GTK_VERSION == 2
gint x, y;
cairo_t *context;
#endif
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;
}
#if GTK_VERSION == 2
x = event->area.x;
y = event->area.y;
context = gdk_cairo_create(gtk_widget_get_window (gpdata->drawing_area));
cairo_rectangle(context, x, y, event->area.width, event->area.height);
#else
cairo_rectangle(context, 0, 0, gtk_widget_get_allocated_width(widget), gtk_widget_get_allocated_height(widget));
#endif
gdk_cairo_set_source_pixbuf(context, buffer, 0, 0);
cairo_fill(context);
#if GTK_VERSION == 2
cairo_destroy(context);
#endif
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 | GDK_SCROLL_MASK);
gtk_widget_set_can_focus(gpdata->drawing_area, TRUE);
#if GTK_VERSION == 3
g_signal_connect(G_OBJECT(gpdata->drawing_area), "draw", G_CALLBACK(remmina_plugin_vnc_on_draw), gp);
#elif GTK_VERSION == 2
g_signal_connect(G_OBJECT(gpdata->drawing_area), "expose-event", G_CALLBACK(remmina_plugin_vnc_on_expose), gp);
#endif
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;
}