/*
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2 as
* published by the Free Software Foundation.
*
* GTK VNC Widget
*/
#include "vncdisplay.h"
#include "coroutine.h"
#include "gvnc.h"
#include "vncshmimage.h"
#include "utils.h"
#include "vncmarshal.h"
#include <gtk/gtk.h>
#include <string.h>
#include <stdlib.h>
#include <gdk/gdkkeysyms.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#define VNC_DISPLAY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), VNC_TYPE_DISPLAY, VncDisplayPrivate))
struct _VncDisplayPrivate
{
int fd;
char *host;
char *port;
GdkGC *gc;
VncShmImage *shm_image;
GdkCursor *null_cursor;
GdkCursor *remote_cursor;
struct gvnc_framebuffer fb;
struct coroutine coroutine;
struct gvnc *gvnc;
gboolean in_pointer_grab;
gboolean in_keyboard_grab;
int button_mask;
int last_x;
int last_y;
gboolean absolute;
gboolean use_shm;
gboolean grab_pointer;
gboolean grab_keyboard;
gboolean local_pointer;
};
G_DEFINE_TYPE(VncDisplay, vnc_display, GTK_TYPE_DRAWING_AREA)
/* Signals */
typedef enum
{
VNC_POINTER_GRAB,
VNC_POINTER_UNGRAB,
VNC_KEYBOARD_GRAB,
VNC_KEYBOARD_UNGRAB,
VNC_CONNECTED,
VNC_INITIALIZED,
VNC_DISCONNECTED,
VNC_AUTH_CREDENTIAL,
VNC_DESKTOP_RESIZE,
VNC_AUTH_FAILURE,
VNC_AUTH_UNSUPPORTED,
VNC_SERVER_CUT_TEXT,
VNC_BELL,
LAST_SIGNAL
} vnc_display_signals;
static guint signals[LAST_SIGNAL] = { 0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0, 0,};
static GParamSpec *signalCredParam;
GtkWidget *vnc_display_new(void)
{
return GTK_WIDGET(g_object_new(VNC_TYPE_DISPLAY, NULL));
}
static GdkCursor *create_null_cursor(void)
{
GdkBitmap *image;
gchar data[4] = {0};
GdkColor fg = { 0, 0, 0, 0 };
GdkCursor *cursor;
image = gdk_bitmap_create_from_data(NULL, data, 1, 1);
cursor = gdk_cursor_new_from_pixmap(GDK_PIXMAP(image),
GDK_PIXMAP(image),
&fg, &fg, 0, 0);
gdk_bitmap_unref(image);
return cursor;
}
static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose,
gpointer data G_GNUC_UNUSED)
{
VncDisplay *obj = VNC_DISPLAY(widget);
VncDisplayPrivate *priv = obj->priv;
int x, y, w, h;
GdkRectangle drawn;
GdkRegion *clear, *copy;
if (priv->shm_image == NULL)
return TRUE;
x = MIN(expose->area.x, priv->fb.width);
y = MIN(expose->area.y, priv->fb.height);
w = MIN(expose->area.x + expose->area.width, priv->fb.width);
h = MIN(expose->area.y + expose->area.height, priv->fb.height);
w -= x;
h -= y;
drawn.x = x;
drawn.y = y;
drawn.width = w;
drawn.height = h;
clear = gdk_region_rectangle(&expose->area);
copy = gdk_region_rectangle(&drawn);
gdk_region_subtract(clear, copy);
gdk_gc_set_clip_region(priv->gc, copy);
vnc_shm_image_draw(priv->shm_image, widget->window,
priv->gc,
x, y, x, y, w, h);
gdk_gc_set_clip_region(priv->gc, clear);
gdk_draw_rectangle(widget->window, priv->gc, TRUE, expose->area.x, expose->area.y,
expose->area.width, expose->area.height);
gdk_region_destroy(clear);
gdk_region_destroy(copy);
return TRUE;
}
static void do_keyboard_grab(VncDisplay *obj, gboolean quiet)
{
VncDisplayPrivate *priv = obj->priv;
gdk_keyboard_grab(GTK_WIDGET(obj)->window,
FALSE,
GDK_CURRENT_TIME);
priv->in_keyboard_grab = TRUE;
if (!quiet)
g_signal_emit(obj, signals[VNC_KEYBOARD_GRAB], 0);
}
static void do_keyboard_ungrab(VncDisplay *obj, gboolean quiet)
{
VncDisplayPrivate *priv = obj->priv;
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
priv->in_keyboard_grab = FALSE;
if (!quiet)
g_signal_emit(obj, signals[VNC_KEYBOARD_UNGRAB], 0);
}
static void do_pointer_grab(VncDisplay *obj, gboolean quiet)
{
VncDisplayPrivate *priv = obj->priv;
/* If we're not already grabbing keyboard, grab it now */
if (!priv->grab_keyboard)
do_keyboard_grab(obj, quiet);
gdk_pointer_grab(GTK_WIDGET(obj)->window,
TRUE,
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_SCROLL_MASK,
GTK_WIDGET(obj)->window,
priv->remote_cursor ? priv->remote_cursor : priv->null_cursor,
GDK_CURRENT_TIME);
priv->in_pointer_grab = TRUE;
if (!quiet)
g_signal_emit(obj, signals[VNC_POINTER_GRAB], 0);
}
static void do_pointer_ungrab(VncDisplay *obj, gboolean quiet)
{
VncDisplayPrivate *priv = obj->priv;
/* If we grabed keyboard upon pointer grab, then ungrab it now */
if (!priv->grab_keyboard)
do_keyboard_ungrab(obj, quiet);
gdk_pointer_ungrab(GDK_CURRENT_TIME);
priv->in_pointer_grab = FALSE;
if (!quiet)
g_signal_emit(obj, signals[VNC_POINTER_UNGRAB], 0);
}
static void do_pointer_hide(VncDisplay *obj)
{
VncDisplayPrivate *priv = obj->priv;
gdk_window_set_cursor(GTK_WIDGET(obj)->window,
priv->remote_cursor ? priv->remote_cursor : priv->null_cursor);
}
static void do_pointer_show(VncDisplay *obj)
{
VncDisplayPrivate *priv = obj->priv;
gdk_window_set_cursor(GTK_WIDGET(obj)->window,
priv->remote_cursor);
}
static gboolean button_event(GtkWidget *widget, GdkEventButton *button,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
int n;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if ((priv->grab_pointer || !priv->absolute) &&
!priv->in_pointer_grab &&
button->button == 1 && button->type == GDK_BUTTON_PRESS)
do_pointer_grab(VNC_DISPLAY(widget), FALSE);
n = 1 << (button->button - 1);
if (button->type == GDK_BUTTON_PRESS)
priv->button_mask |= n;
else if (button->type == GDK_BUTTON_RELEASE)
priv->button_mask &= ~n;
if (priv->absolute) {
gvnc_pointer_event(priv->gvnc, priv->button_mask,
priv->last_x, priv->last_y);
} else {
gvnc_pointer_event(priv->gvnc, priv->button_mask,
0x7FFF, 0x7FFF);
}
return TRUE;
}
static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
int mask;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if (scroll->direction == GDK_SCROLL_UP)
mask = (1 << 3);
else if (scroll->direction == GDK_SCROLL_DOWN)
mask = (1 << 4);
else if (scroll->direction == GDK_SCROLL_LEFT)
mask = (1 << 5);
else if (scroll->direction == GDK_SCROLL_RIGHT)
mask = (1 << 6);
else
return TRUE;
if (priv->absolute) {
gvnc_pointer_event(priv->gvnc, priv->button_mask | mask,
priv->last_x, priv->last_y);
gvnc_pointer_event(priv->gvnc, priv->button_mask,
priv->last_x, priv->last_y);
} else {
gvnc_pointer_event(priv->gvnc, priv->button_mask | mask,
0x7FFF, 0x7FFF);
gvnc_pointer_event(priv->gvnc, priv->button_mask,
0x7FFF, 0x7FFF);
}
return TRUE;
}
static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
int dx, dy;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if (!priv->absolute && !priv->in_pointer_grab)
return TRUE;
if (!priv->absolute && priv->in_pointer_grab) {
GdkDrawable *drawable = GDK_DRAWABLE(widget->window);
GdkDisplay *display = gdk_drawable_get_display(drawable);
GdkScreen *screen = gdk_drawable_get_screen(drawable);
int x = (int)motion->x_root;
int y = (int)motion->y_root;
if (x == 0) x += 200;
if (y == 0) y += 200;
if (x == (gdk_screen_get_width(screen) - 1)) x -= 200;
if (y == (gdk_screen_get_height(screen) - 1)) y -= 200;
if (x != (int)motion->x_root || y != (int)motion->y_root) {
gdk_display_warp_pointer(display, screen, x, y);
priv->last_x = -1;
priv->last_y = -1;
return TRUE;
}
}
if (priv->last_x != -1) {
if (priv->absolute) {
dx = (int)motion->x;
dy = (int)motion->y;
} else {
dx = (int)motion->x + 0x7FFF - priv->last_x;
dy = (int)motion->y + 0x7FFF - priv->last_y;
}
gvnc_pointer_event(priv->gvnc, priv->button_mask, dx, dy);
}
priv->last_x = (int)motion->x;
priv->last_y = (int)motion->y;
return TRUE;
}
static gboolean key_event(GtkWidget *widget, GdkEventKey *key,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
guint keyval;
gint group, level;
GdkModifierType consumed;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
/*
* Key handling in VNC is screwy. The event.keyval from GTK is
* interpreted relative to modifier state. This really messes
* up with VNC which has no concept of modifiers - it just sees
* key up & down events - the remote end interprets modifiers
* itself. So if we interpret at the client end you can end up
* with 'Alt' key press generating Alt_L, and key release generating
* ISO_Prev_Group. This really really confuses the VNC server
* with 'Alt' getting stuck on.
*
* So we have to redo GTK's keycode -> keyval translation
* using only the SHIFT modifier which the RFB explicitly
* requires to be interpreted at client end.
*
* Arggggh.
*/
gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(),
key->hardware_keycode,
key->state & (GDK_SHIFT_MASK | GDK_LOCK_MASK),
key->group,
&keyval,
&group,
&level,
&consumed);
gvnc_key_event(priv->gvnc, key->type == GDK_KEY_PRESS ? 1 : 0, keyval);
if (key->type == GDK_KEY_PRESS &&
((keyval == GDK_Control_L && (key->state & GDK_MOD1_MASK)) ||
(keyval == GDK_Alt_L && (key->state & GDK_CONTROL_MASK)))) {
if (priv->in_pointer_grab)
do_pointer_ungrab(VNC_DISPLAY(widget), FALSE);
else
do_pointer_grab(VNC_DISPLAY(widget), FALSE);
}
return TRUE;
}
static gboolean enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if (crossing->mode != GDK_CROSSING_NORMAL)
return TRUE;
if (priv->grab_keyboard)
do_keyboard_grab(VNC_DISPLAY(widget), FALSE);
return TRUE;
}
static gboolean leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
gpointer data G_GNUC_UNUSED)
{
VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if (crossing->mode != GDK_CROSSING_NORMAL)
return TRUE;
if (priv->grab_keyboard)
do_keyboard_ungrab(VNC_DISPLAY(widget), FALSE);
return TRUE;
}
static gboolean on_update(void *opaque, int x, int y, int w, int h)
{
GtkWidget *obj = GTK_WIDGET(opaque);
gtk_widget_queue_draw_area(obj, x, y, w, h);
return TRUE;
}
static gboolean on_resize(void *opaque, int width, int height)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
GdkVisual *visual;
if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
return TRUE;
if (priv->shm_image)
g_object_unref(priv->shm_image);
if (priv->fb.shm_id != -1)
shmctl(priv->fb.shm_id, IPC_RMID, NULL);
if (priv->gc == NULL) {
priv->null_cursor = create_null_cursor();
if (priv->local_pointer)
do_pointer_show(obj);
else
do_pointer_hide(obj);
priv->gc = gdk_gc_new(GTK_WIDGET(obj)->window);
}
visual = gdk_drawable_get_visual(GTK_WIDGET(obj)->window);
priv->shm_image = vnc_shm_image_new(visual, width, height, priv->use_shm);
priv->fb.shm_id = priv->shm_image->shmid;
priv->fb.red_mask = visual->red_mask >> visual->red_shift;
priv->fb.green_mask = visual->green_mask >> visual->green_shift;
priv->fb.blue_mask = visual->blue_mask >> visual->blue_shift;
priv->fb.red_shift = visual->red_shift;
priv->fb.green_shift = visual->green_shift;
priv->fb.blue_shift = visual->blue_shift;
priv->fb.depth = priv->shm_image->depth;
priv->fb.bpp = priv->shm_image->bpp;
priv->fb.width = priv->shm_image->width;
priv->fb.height = priv->shm_image->height;
priv->fb.linesize = priv->shm_image->bytes_per_line;
priv->fb.data = (uint8_t *)priv->shm_image->pixels;
if (gvnc_shared_memory_enabled(priv->gvnc) && priv->fb.shm_id != -1)
gvnc_set_shared_buffer(priv->gvnc,
priv->fb.linesize, priv->fb.shm_id);
gtk_widget_set_size_request(GTK_WIDGET(obj), width, height);
gvnc_set_local(priv->gvnc, &priv->fb);
g_signal_emit (G_OBJECT (obj),
signals[VNC_DESKTOP_RESIZE],
0,
width, height);
return TRUE;
}
static gboolean on_pointer_type_change(void *opaque, int absolute)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
if (absolute && priv->in_pointer_grab && !priv->grab_pointer)
do_pointer_ungrab(obj, FALSE);
priv->absolute = absolute;
return TRUE;
}
static gboolean on_shared_memory_rmid(void *opaque, int shmid)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
/* this needs to be much more robust */
if (shmid != priv->fb.shm_id) {
printf("server sent wrong shmid!\n");
}
shmctl(shmid, IPC_RMID, NULL);
if (shmid == priv->fb.shm_id)
priv->fb.shm_id = -1;
return TRUE;
}
static gboolean on_auth_cred(void *opaque)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
GValueArray *credList;
GValue username, password, clientname;
memset(&username, 0, sizeof(username));
memset(&password, 0, sizeof(password));
memset(&clientname, 0, sizeof(clientname));
credList = g_value_array_new(2);
if (gvnc_wants_credential_username(obj->priv->gvnc)) {
g_value_init(&username, G_PARAM_SPEC_VALUE_TYPE(signalCredParam));
g_value_set_enum(&username, VNC_DISPLAY_CREDENTIAL_USERNAME);
g_value_array_append(credList, &username);
}
if (gvnc_wants_credential_password(obj->priv->gvnc)) {
g_value_init(&password, G_PARAM_SPEC_VALUE_TYPE(signalCredParam));
g_value_set_enum(&password, VNC_DISPLAY_CREDENTIAL_PASSWORD);
g_value_array_append(credList, &password);
}
if (gvnc_wants_credential_x509(obj->priv->gvnc)) {
g_value_init(&clientname, G_PARAM_SPEC_VALUE_TYPE(signalCredParam));
g_value_set_enum(&clientname, VNC_DISPLAY_CREDENTIAL_CLIENTNAME);
g_value_array_append(credList, &clientname);
}
g_signal_emit (G_OBJECT (obj),
signals[VNC_AUTH_CREDENTIAL],
0,
credList);
g_value_array_free(credList);
return TRUE;
}
static gboolean on_auth_type(void *opaque, unsigned int ntype, unsigned int *types)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
/*
* XXX lame - we should have some prioritization. That
* said most servers only support 1 auth type at any time
*/
if (ntype)
gvnc_set_auth_type(priv->gvnc, types[0]);
return TRUE;
}
static gboolean on_auth_subtype(void *opaque, unsigned int ntype, unsigned int *types)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
/*
* XXX lame - we should have some prioritization. That
* said most servers only support 1 auth type at any time
*/
if (ntype)
gvnc_set_auth_subtype(priv->gvnc, types[0]);
return TRUE;
}
static gboolean on_auth_failure(void *opaque, const char *msg)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
g_signal_emit (G_OBJECT (obj),
signals[VNC_AUTH_FAILURE],
0,
msg);
return TRUE;
}
static gboolean on_auth_unsupported(void *opaque, unsigned int auth_type)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
g_signal_emit (G_OBJECT (obj),
signals[VNC_AUTH_UNSUPPORTED],
0,
auth_type);
return TRUE;
}
static gboolean on_server_cut_text(void *opaque, const void* text, size_t len)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
GString *str = g_string_new_len ((const gchar *)text, len);
g_signal_emit (G_OBJECT (obj),
signals[VNC_SERVER_CUT_TEXT],
0,
str->str);
g_string_free (str, TRUE);
return TRUE;
}
static gboolean on_bell(void *opaque)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
g_signal_emit (G_OBJECT (obj),
signals[VNC_BELL],
0);
return TRUE;
}
static gboolean on_local_cursor(void *opaque, int x, int y, int width, int height, uint8_t *image)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
if (priv->remote_cursor) {
gdk_cursor_unref(priv->remote_cursor);
priv->remote_cursor = NULL;
}
if (width && height) {
GdkDisplay *display = gdk_drawable_get_display(GDK_DRAWABLE(GTK_WIDGET(obj)->window));
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(image, GDK_COLORSPACE_RGB,
TRUE, 8, width, height,
width * 4, NULL, NULL);
priv->remote_cursor = gdk_cursor_new_from_pixbuf(display,
pixbuf,
x, y);
gdk_pixbuf_unref(pixbuf);
}
if (priv->in_pointer_grab) {
do_pointer_ungrab(obj, TRUE);
do_pointer_grab(obj, TRUE);
} else {
do_pointer_hide(obj);
}
return TRUE;
}
static const struct gvnc_ops vnc_display_ops = {
.auth_cred = on_auth_cred,
.auth_type = on_auth_type,
.auth_subtype = on_auth_subtype,
.auth_failure = on_auth_failure,
.update = on_update,
.resize = on_resize,
.pointer_type_change = on_pointer_type_change,
.shared_memory_rmid = on_shared_memory_rmid,
.local_cursor = on_local_cursor,
.auth_unsupported = on_auth_unsupported,
.server_cut_text = on_server_cut_text,
.bell = on_bell
};
static void *vnc_coroutine(void *opaque)
{
VncDisplay *obj = VNC_DISPLAY(opaque);
VncDisplayPrivate *priv = obj->priv;
int32_t encodings[] = { GVNC_ENCODING_DESKTOP_RESIZE,
GVNC_ENCODING_RICH_CURSOR,
GVNC_ENCODING_XCURSOR,
GVNC_ENCODING_SHARED_MEMORY,
GVNC_ENCODING_POINTER_CHANGE,
GVNC_ENCODING_HEXTILE,
GVNC_ENCODING_COPY_RECT,
GVNC_ENCODING_RAW };
int ret;
if (priv->gvnc == NULL || gvnc_is_open(priv->gvnc)) {
g_object_unref(G_OBJECT(obj));
return NULL;
}
GVNC_DEBUG("Started background coroutine\n");
if (priv->fd != -1) {
if (!gvnc_open_fd(priv->gvnc, priv->fd))
goto cleanup;
} else {
if (!gvnc_open_host(priv->gvnc, priv->host, priv->port))
goto cleanup;
}
g_signal_emit (G_OBJECT (obj),
signals[VNC_CONNECTED],
0);
GVNC_DEBUG("Protocol initialization\n");
if (!gvnc_initialize(priv->gvnc, FALSE))
goto cleanup;
g_signal_emit (G_OBJECT (obj),
signals[VNC_INITIALIZED],
0);
if (!gvnc_set_encodings(priv->gvnc, 6, encodings))
goto cleanup;
if (!gvnc_framebuffer_update_request(priv->gvnc, 0, 0, 0, priv->fb.width, priv->fb.height))
goto cleanup;
GVNC_DEBUG("Running main loop\n");
while ((ret = gvnc_server_message(priv->gvnc))) {
if (!gvnc_framebuffer_update_request(priv->gvnc, 1, 0, 0,
priv->fb.width, priv->fb.height))
goto cleanup;
}
cleanup:
GVNC_DEBUG("Doing final VNC cleanup\n");
gvnc_close(priv->gvnc);
g_signal_emit (G_OBJECT (obj),
signals[VNC_DISCONNECTED],
0);
g_object_unref(G_OBJECT(obj));
/* Co-routine exits now - the VncDisplay object may no longer exist,
so don't do anything else now unless you like SEGVs */
return NULL;
}
static gboolean do_vnc_display_open(gpointer data)
{
VncDisplay *obj = VNC_DISPLAY(data);
struct coroutine *co;
if (obj->priv->gvnc == NULL || gvnc_is_open(obj->priv->gvnc)) {
g_object_unref(G_OBJECT(obj));
return FALSE;
}
co = &obj->priv->coroutine;
co->stack_size = 16 << 20;
co->entry = vnc_coroutine;
co->release = NULL;
coroutine_init(co);
coroutine_yieldto(co, obj);
return FALSE;
}
gboolean vnc_display_open_fd(VncDisplay *obj, int fd)
{
if (obj->priv->gvnc == NULL || gvnc_is_open(obj->priv->gvnc))
return FALSE;
obj->priv->fd = fd;
obj->priv->host = NULL;
obj->priv->port = NULL;
g_object_ref(G_OBJECT(obj)); /* Unref'd when co-routine exits */
g_idle_add(do_vnc_display_open, obj);
return TRUE;
}
gboolean vnc_display_open_host(VncDisplay *obj, const char *host, const char *port)
{
if (obj->priv->gvnc == NULL || gvnc_is_open(obj->priv->gvnc))
return FALSE;
obj->priv->host = strdup(host);
if (!obj->priv->host) {
return FALSE;
}
obj->priv->port = strdup(port);
if (!obj->priv->port) {
free(obj->priv->host);
obj->priv->host = NULL;
return FALSE;
}
g_object_ref(G_OBJECT(obj)); /* Unref'd when co-routine exits */
g_idle_add(do_vnc_display_open, obj);
return TRUE;
}
gboolean vnc_display_is_open(VncDisplay *obj)
{
if (obj->priv->gvnc == NULL)
return FALSE;
return gvnc_is_open(obj->priv->gvnc);
}
void vnc_display_close(VncDisplay *obj)
{
if (obj->priv->gvnc == NULL)
return;
if (gvnc_is_open(obj->priv->gvnc)) {
GVNC_DEBUG("Requesting graceful shutdown of connection\n");
gvnc_shutdown(obj->priv->gvnc);
}
}
void vnc_display_send_keys(VncDisplay *obj, const guint *keyvals, int nkeyvals)
{
int i;
if (obj->priv->gvnc == NULL || !gvnc_is_open(obj->priv->gvnc))
return;
for (i = 0 ; i < nkeyvals ; i++)
gvnc_key_event(obj->priv->gvnc, 1, keyvals[i]);
for (i = (nkeyvals-1) ; i >= 0 ; i--)
gvnc_key_event(obj->priv->gvnc, 0, keyvals[i]);
}
static void vnc_display_destroy (GtkObject *obj)
{
VncDisplay *display = VNC_DISPLAY (obj);
GVNC_DEBUG("Requesting that VNC close\n");
vnc_display_close(display);
GTK_OBJECT_CLASS (vnc_display_parent_class)->destroy (obj);
}
static void vnc_display_finalize (GObject *obj)
{
VncDisplay *display = VNC_DISPLAY (obj);
GVNC_DEBUG("Releasing VNC widget\n");
if (gvnc_is_open(display->priv->gvnc)) {
g_warning("VNC widget finalized before the connection finished shutting down");
}
gvnc_free(display->priv->gvnc);
display->priv->gvnc = NULL;
G_OBJECT_CLASS (vnc_display_parent_class)->finalize (obj);
}
static void vnc_display_class_init(VncDisplayClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
object_class->finalize = vnc_display_finalize;
gtkobject_class->destroy = vnc_display_destroy;
signalCredParam = g_param_spec_enum("credential",
"credential",
"credential",
vnc_display_credential_get_type(),
0,
G_PARAM_READABLE);
signals[VNC_CONNECTED] =
g_signal_new ("vnc-connected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (VncDisplayClass, vnc_connected),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_INITIALIZED] =
g_signal_new ("vnc-initialized",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (VncDisplayClass, vnc_initialized),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_DISCONNECTED] =
g_signal_new ("vnc-disconnected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (VncDisplayClass, vnc_disconnected),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_AUTH_CREDENTIAL] =
g_signal_new ("vnc-auth-credential",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (VncDisplayClass, vnc_auth_credential),
NULL, NULL,
g_cclosure_marshal_VOID__PARAM,
G_TYPE_NONE,
1,
G_TYPE_VALUE_ARRAY);
signals[VNC_POINTER_GRAB] =
g_signal_new("vnc-pointer-grab",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_POINTER_UNGRAB] =
g_signal_new("vnc-pointer-ungrab",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_KEYBOARD_GRAB] =
g_signal_new("vnc-keyboard-grab",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_KEYBOARD_UNGRAB] =
g_signal_new("vnc-keyboard-ungrab",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[VNC_DESKTOP_RESIZE] =
g_signal_new("vnc-desktop-resize",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_user_marshal_VOID__INT_INT,
G_TYPE_NONE,
2,
G_TYPE_INT, G_TYPE_INT);
signals[VNC_AUTH_FAILURE] =
g_signal_new("vnc-auth-failure",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE,
1,
G_TYPE_STRING);
signals[VNC_AUTH_UNSUPPORTED] =
g_signal_new("vnc-auth-unsupported",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE,
1,
G_TYPE_UINT);
signals[VNC_SERVER_CUT_TEXT] =
g_signal_new("vnc-server-cut-text",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE,
1,
G_TYPE_STRING);
signals[VNC_BELL] =
g_signal_new("vnc-bell",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
g_type_class_add_private(klass, sizeof(VncDisplayPrivate));
}
static void vnc_display_init(VncDisplay *display)
{
GtkObject *obj = GTK_OBJECT(display);
GtkWidget *widget = GTK_WIDGET(display);
g_signal_connect(obj, "expose-event",
G_CALLBACK(expose_event), NULL);
g_signal_connect(obj, "motion-notify-event",
G_CALLBACK(motion_event), NULL);
g_signal_connect(obj, "button-press-event",
G_CALLBACK(button_event), NULL);
g_signal_connect(obj, "button-release-event",
G_CALLBACK(button_event), NULL);
g_signal_connect(obj, "scroll-event",
G_CALLBACK(scroll_event), NULL);
g_signal_connect(obj, "key-press-event",
G_CALLBACK(key_event), NULL);
g_signal_connect(obj, "key-release-event",
G_CALLBACK(key_event), NULL);
g_signal_connect(obj, "enter-notify-event",
G_CALLBACK(enter_event), NULL);
g_signal_connect(obj, "leave-notify-event",
G_CALLBACK(leave_event), NULL);
GTK_WIDGET_SET_FLAGS(obj, GTK_CAN_FOCUS);
gtk_widget_add_events(widget,
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_SCROLL_MASK |
GDK_KEY_PRESS_MASK);
gtk_widget_set_double_buffered(widget, FALSE);
display->priv = VNC_DISPLAY_GET_PRIVATE(display);
memset(display->priv, 0, sizeof(VncDisplayPrivate));
display->priv->last_x = -1;
display->priv->last_y = -1;
display->priv->absolute = 1;
display->priv->fb.shm_id = -1;
display->priv->fd = -1;
display->priv->gvnc = gvnc_new(&vnc_display_ops, obj);
}
static int vnc_display_best_path(char *buf,
int buflen,
const char *basedir,
const char *basefile,
char **dirs,
unsigned int ndirs)
{
unsigned int i;
for (i = 0 ; i < ndirs ; i++) {
struct stat sb;
snprintf(buf, buflen-1, "%s/%s/%s", dirs[i], basedir, basefile);
buf[buflen-1] = '\0';
if (stat(buf, &sb) == 0)
return 0;
}
return -1;
}
static int vnc_display_set_x509_credential(VncDisplay *obj, const char *name)
{
char sysdir[PATH_MAX], userdir[PATH_MAX];
struct passwd *pw;
char file[PATH_MAX];
char *dirs[] = { sysdir, userdir };
strncpy(sysdir, SYSCONFDIR "/pki", PATH_MAX-1);
sysdir[PATH_MAX-1] = '\0';
if (!(pw = getpwuid(getuid())))
return TRUE;
snprintf(userdir, PATH_MAX-1, "%s/.pki", pw->pw_dir);
userdir[PATH_MAX-1] = '\0';
if (vnc_display_best_path(file, PATH_MAX, "CA", "cacert.pem", dirs, 2) < 0)
return TRUE;
gvnc_set_credential_x509_cacert(obj->priv->gvnc, file);
/* Don't mind failures of CRL */
if (vnc_display_best_path(file, PATH_MAX, "CA", "cacrl.pem", dirs, 2) == 0)
gvnc_set_credential_x509_cacert(obj->priv->gvnc, file);
/* Set client key & cert if we have them. Server will reject auth
* if it decides it requires them*/
if (vnc_display_best_path(file, PATH_MAX, name, "private/clientkey.pem", dirs, 2) == 0)
gvnc_set_credential_x509_key(obj->priv->gvnc, file);
if (vnc_display_best_path(file, PATH_MAX, name, "clientcert.pem", dirs, 2) == 0)
gvnc_set_credential_x509_cert(obj->priv->gvnc, file);
return FALSE;
}
gboolean vnc_display_set_credential(VncDisplay *obj, int type, const gchar *data)
{
switch (type) {
case VNC_DISPLAY_CREDENTIAL_PASSWORD:
if (gvnc_set_credential_password(obj->priv->gvnc, data))
return FALSE;
return TRUE;
case VNC_DISPLAY_CREDENTIAL_USERNAME:
if (gvnc_set_credential_username(obj->priv->gvnc, data))
return FALSE;
return TRUE;
case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
return vnc_display_set_x509_credential(obj, data);
}
return FALSE;
}
void vnc_display_set_use_shm(VncDisplay *obj, gboolean enable)
{
obj->priv->use_shm = enable;
}
void vnc_display_set_pointer_local(VncDisplay *obj, gboolean enable)
{
if (obj->priv->gc) {
if (enable)
do_pointer_show(obj);
else
do_pointer_hide(obj);
}
obj->priv->local_pointer = enable;
}
void vnc_display_set_pointer_grab(VncDisplay *obj, gboolean enable)
{
VncDisplayPrivate *priv = obj->priv;
priv->grab_pointer = enable;
if (!enable && priv->absolute && priv->in_pointer_grab)
do_pointer_ungrab(obj, FALSE);
if (enable && priv->absolute && !priv->in_pointer_grab)
do_pointer_grab(obj, FALSE);
}
void vnc_display_set_keyboard_grab(VncDisplay *obj, gboolean enable)
{
VncDisplayPrivate *priv = obj->priv;
priv->grab_keyboard = enable;
if (!enable && priv->in_keyboard_grab && !priv->in_pointer_grab)
do_keyboard_ungrab(obj, FALSE);
if (enable && !priv->in_keyboard_grab)
do_keyboard_grab(obj, FALSE);
}
GType vnc_display_credential_get_type(void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
{ VNC_DISPLAY_CREDENTIAL_PASSWORD, "VNC_DISPLAY_CREDENTIAL_PASSWORD", "password" },
{ VNC_DISPLAY_CREDENTIAL_USERNAME, "VNC_DISPLAY_CREDENTIAL_USERNAME", "username" },
{ VNC_DISPLAY_CREDENTIAL_CLIENTNAME, "VNC_DISPLAY_CREDENTIAL_CLIENTNAME", "clientname" },
{ 0, NULL, NULL }
};
etype = g_enum_register_static ("VncDisplayCredentialType", values );
}
return etype;
}
GdkPixbuf *vnc_display_get_pixbuf(VncDisplay *obj)
{
if (!obj->priv->gvnc ||
!gvnc_is_initialized(obj->priv->gvnc))
return NULL;
return vnc_shm_image_get_pixbuf(obj->priv->shm_image);
}
int vnc_display_get_width(VncDisplay *obj)
{
g_return_val_if_fail (VNC_IS_DISPLAY (obj), -1);
return gvnc_get_width (obj->priv->gvnc);
}
int vnc_display_get_height(VncDisplay *obj)
{
g_return_val_if_fail (VNC_IS_DISPLAY (obj), -1);
return gvnc_get_height (obj->priv->gvnc);
}
const char * vnc_display_get_name(VncDisplay *obj)
{
g_return_val_if_fail (VNC_IS_DISPLAY (obj), NULL);
return gvnc_get_name (obj->priv->gvnc);
}
void vnc_display_client_cut_text(VncDisplay *obj, const gchar *text)
{
g_return_if_fail (VNC_IS_DISPLAY (obj));
gvnc_client_cut_text(obj->priv->gvnc, text, strlen (text));
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* tab-width: 8
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1