/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2010 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#include "spice-widget.h"
#include "spice-common.h"
#include "vncdisplaykeymap.h"
#include
#include
#include
#include
#include
#include
#include
#define SPICE_DISPLAY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY, spice_display))
struct spice_display {
gint channel_id;
/* options */
bool keyboard_grab_enable;
bool mouse_grab_enable;
bool resize_guest_enable;
bool auto_clipboard_enable;
/* state */
enum SpiceSurfaceFmt format;
gint width, height, stride;
gint shmid;
gpointer data;
gint ww, wh, mx, my;
bool convert;
bool have_mitshm;
Display *dpy;
XVisualInfo *vi;
XImage *ximage;
XShmSegmentInfo *shminfo;
GC gc;
GtkClipboard *clipboard;
bool clip_hasdata;
bool clip_grabbed;
SpiceSession *session;
SpiceMainChannel *main;
SpiceChannel *display;
SpiceCursorChannel *cursor;
SpiceInputsChannel *inputs;
enum SpiceMouseMode mouse_mode;
int mouse_grab_active;
bool mouse_have_pointer;
GdkCursor *mouse_cursor;
int mouse_last_x;
int mouse_last_y;
int mouse_guest_x;
int mouse_guest_y;
bool keyboard_grab_active;
bool keyboard_have_focus;
int keyboard_grab_count;
time_t keyboard_grab_time;
const guint16 const *keycode_map;
size_t keycode_maplen;
uint32_t key_state[512 / 32];
VncGrabSequence *grabseq; /* the configured key sequence */
gboolean *activeseq; /* the currently pressed keys */
gint mark;
};
G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_DRAWING_AREA)
/* Properties */
enum {
PROP_0,
PROP_KEYBOARD_GRAB,
PROP_MOUSE_GRAB,
PROP_RESIZE_GUEST,
PROP_AUTO_CLIPBOARD,
};
/* Signals */
enum {
SPICE_DISPLAY_MOUSE_GRAB,
SPICE_DISPLAY_KEYBOARD_GRAB,
SPICE_DISPLAY_LAST_SIGNAL,
};
static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
static bool no_mitshm;
static void try_keyboard_grab(GtkWidget *widget);
static void try_keyboard_ungrab(GtkWidget *widget);
static void try_mouse_grab(GtkWidget *widget);
static void try_mouse_ungrab(GtkWidget *widget);
static void recalc_geometry(GtkWidget *widget);
static void clipboard_owner_change(GtkClipboard *clipboard,
GdkEventOwnerChange *event, gpointer user_data);
static void disconnect_main(SpiceDisplay *display);
static void disconnect_cursor(SpiceDisplay *display);
static void disconnect_display(SpiceDisplay *display);
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);
static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data);
/* ---------------------------------------------------------------- */
static struct format_table {
enum SpiceSurfaceFmt spice;
XVisualInfo xvisual;
} format_table[] = {
{
.spice = SPICE_SURFACE_FMT_32_xRGB,
.xvisual = {
.depth = 24,
.red_mask = 0xff0000,
.green_mask = 0x00ff00,
.blue_mask = 0x0000ff,
},
},{
.spice = SPICE_SURFACE_FMT_16_555,
.xvisual = {
.depth = 16,
.red_mask = 0x7c00,
.green_mask = 0x03e0,
.blue_mask = 0x001f,
},
},{
.spice = SPICE_SURFACE_FMT_16_565,
.xvisual = {
.depth = 16,
.red_mask = 0xf800,
.green_mask = 0x07e0,
.blue_mask = 0x001f,
},
}
};
/* ---------------------------------------------------------------- */
static void spice_display_get_property(GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpiceDisplay *display = SPICE_DISPLAY(object);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
switch (prop_id) {
case PROP_KEYBOARD_GRAB:
g_value_set_boolean(value, d->keyboard_grab_enable);
break;
case PROP_MOUSE_GRAB:
g_value_set_boolean(value, d->mouse_grab_enable);
break;
case PROP_RESIZE_GUEST:
g_value_set_boolean(value, d->resize_guest_enable);
break;
case PROP_AUTO_CLIPBOARD:
g_value_set_boolean(value, d->auto_clipboard_enable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void spice_display_set_property(GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpiceDisplay *display = SPICE_DISPLAY(object);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
switch (prop_id) {
case PROP_KEYBOARD_GRAB:
d->keyboard_grab_enable = g_value_get_boolean(value);
if (d->keyboard_grab_enable) {
try_keyboard_grab(GTK_WIDGET(display));
} else {
try_keyboard_ungrab(GTK_WIDGET(display));
}
break;
case PROP_MOUSE_GRAB:
d->mouse_grab_enable = g_value_get_boolean(value);
if (!d->mouse_grab_enable) {
try_mouse_ungrab(GTK_WIDGET(display));
}
break;
case PROP_RESIZE_GUEST:
d->resize_guest_enable = g_value_get_boolean(value);
if (d->resize_guest_enable) {
gtk_widget_set_size_request(GTK_WIDGET(display), 640, 480);
recalc_geometry(GTK_WIDGET(display));
} else {
gtk_widget_set_size_request(GTK_WIDGET(display),
d->width, d->height);
}
break;
case PROP_AUTO_CLIPBOARD:
d->auto_clipboard_enable = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void spice_display_destroy(GtkObject *obj)
{
SpiceDisplay *display = SPICE_DISPLAY(obj);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
g_signal_handlers_disconnect_by_func(d->session, G_CALLBACK(channel_new),
display);
g_signal_handlers_disconnect_by_func(d->session, G_CALLBACK(channel_destroy),
display);
g_signal_handlers_disconnect_by_func(d->clipboard, G_CALLBACK(clipboard_owner_change),
display);
disconnect_main(display);
disconnect_display(display);
disconnect_cursor(display);
GTK_OBJECT_CLASS(spice_display_parent_class)->destroy(obj);
}
static void spice_display_finalize(GObject *obj)
{
SpiceDisplay *display = SPICE_DISPLAY(obj);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("Finalize SpiceDisplay");
if (d->grabseq) {
vnc_grab_sequence_free(d->grabseq);
d->grabseq = NULL;
}
G_OBJECT_CLASS(spice_display_parent_class)->finalize(obj);
}
static void spice_display_init(SpiceDisplay *display)
{
GtkWidget *widget = GTK_WIDGET(display);
spice_display *d;
d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
memset(d, 0, sizeof(*d));
gtk_widget_add_events(widget,
GDK_STRUCTURE_MASK |
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_KEY_PRESS_MASK);
gtk_widget_set_double_buffered(widget, false);
gtk_widget_set_can_focus(widget, true);
d->keycode_map = vnc_display_keymap_gdk2xtkbd_table(&d->keycode_maplen);
d->grabseq = vnc_grab_sequence_new_from_string("Control_L+Alt_L");
d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
d->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
g_signal_connect(G_OBJECT(d->clipboard), "owner-change",
G_CALLBACK(clipboard_owner_change), display);
if (g_getenv("SPICE_DEBUG_CURSOR"))
d->mouse_cursor = gdk_cursor_new(GDK_DOT);
else
d->mouse_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
d->have_mitshm = true;
}
static guint32 get_keyboard_lock_modifiers(Display *x_display)
{
XKeyboardState keyboard_state;
guint32 modifiers = 0;
XGetKeyboardControl(x_display, &keyboard_state);
if (keyboard_state.led_mask & 0x01) {
modifiers |= SPICE_INPUTS_CAPS_LOCK;
}
if (keyboard_state.led_mask & 0x02) {
modifiers |= SPICE_INPUTS_NUM_LOCK;
}
if (keyboard_state.led_mask & 0x04) {
modifiers |= SPICE_INPUTS_SCROLL_LOCK;
}
return modifiers;
}
typedef enum SpiceLed {
CAPS_LOCK_LED = 1,
NUM_LOCK_LED,
SCROLL_LOCK_LED,
} SpiceLed;
#if 0
static guint get_modifier_mask(Display *x_display, KeySym modifier)
{
int mask = 0;
int i;
XModifierKeymap* map = XGetModifierMapping(x_display);
KeyCode keycode = XKeysymToKeycode(x_display, modifier);
if (keycode == NoSymbol) {
return 0;
}
for (i = 0; i < 8; i++) {
if (map->modifiermap[map->max_keypermod * i] == keycode) {
mask = 1 << i;
}
}
XFreeModifiermap(map);
return mask;
}
static void set_keyboard_led(Display *x_display, SpiceLed led, int set)
{
guint mask;
XKeyboardControl keyboard_control;
switch (led) {
case CAPS_LOCK_LED:
if ((mask = get_modifier_mask(x_display, XK_Caps_Lock)) != 0) {
XkbLockModifiers(x_display, XkbUseCoreKbd, mask, set ? mask : 0);
}
return;
case NUM_LOCK_LED:
if ((mask = get_modifier_mask(x_display, XK_Num_Lock)) != 0) {
XkbLockModifiers(x_display, XkbUseCoreKbd, mask, set ? mask : 0);
}
return;
case SCROLL_LOCK_LED:
keyboard_control.led_mode = set ? LedModeOn : LedModeOff;
keyboard_control.led = led;
XChangeKeyboardControl(x_display, KBLed | KBLedMode, &keyboard_control);
return;
}
}
static void spice_set_keyboard_lock_modifiers(SpiceDisplay *display, uint32_t modifiers)
{
Display *x_display;
x_display = GDK_WINDOW_XDISPLAY(gtk_widget_get_parent_window(GTK_WIDGET(display)));
set_keyboard_led(x_display, CAPS_LOCK_LED, !!(modifiers & SPICE_INPUTS_CAPS_LOCK));
set_keyboard_led(x_display, NUM_LOCK_LED, !!(modifiers & SPICE_INPUTS_NUM_LOCK));
set_keyboard_led(x_display, SCROLL_LOCK_LED, !!(modifiers & SPICE_INPUTS_SCROLL_LOCK));
}
#endif
static void spice_sync_keyboard_lock_modifiers(SpiceDisplay *display)
{
Display *x_display;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
guint32 modifiers;
x_display = GDK_WINDOW_XDISPLAY(gtk_widget_get_parent_window(GTK_WIDGET(display)));
modifiers = get_keyboard_lock_modifiers(x_display);
if (d->inputs)
spice_inputs_set_key_locks(d->inputs, modifiers);
}
void spice_display_set_grab_keys(SpiceDisplay *display, VncGrabSequence *seq)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
g_return_if_fail(d != NULL);
if (d->grabseq) {
vnc_grab_sequence_free(d->grabseq);
g_free(d->activeseq);
}
if (seq)
d->grabseq = vnc_grab_sequence_copy(seq);
else
d->grabseq = vnc_grab_sequence_new_from_string("Control_L+Alt_L");
d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
}
VncGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
g_return_val_if_fail(d != NULL, NULL);
return d->grabseq;
}
static void try_keyboard_grab(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
time_t now;
GdkGrabStatus status;
if (d->keyboard_grab_active)
return;
if (!d->keyboard_grab_enable)
return;
if (!d->keyboard_have_focus)
return;
if (!d->mouse_have_pointer)
return;
#if 1
/*
* == DEBUG ==
* focus / keyboard grab behavior is funky
* when going fullscreen (with KDE):
* focus-in-event -> grab -> focus-out-event -> ungrab -> repeat
* I have no idea why the grab triggers focus-out :-(
*/
g_return_if_fail(gtk_widget_is_focus(widget));
g_return_if_fail(gtk_widget_has_focus(widget));
now = time(NULL);
if (d->keyboard_grab_time != now) {
d->keyboard_grab_time = now;
d->keyboard_grab_count = 0;
}
if (d->keyboard_grab_count++ > 32) {
g_critical("%s: 32 grabs last second -> emergency exit",
__FUNCTION__);
return;
}
#endif
SPICE_DEBUG("grab keyboard");
status = gdk_keyboard_grab(gtk_widget_get_window(widget), FALSE,
GDK_CURRENT_TIME);
if (status != GDK_GRAB_SUCCESS) {
g_warning("keyboard grab failed %d", status);
d->keyboard_grab_active = false;
} else {
d->keyboard_grab_active = true;
g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, true);
}
}
static void try_keyboard_ungrab(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (!d->keyboard_grab_active)
return;
SPICE_DEBUG("ungrab keyboard");
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
d->keyboard_grab_active = false;
g_signal_emit(widget, signals[SPICE_DISPLAY_KEYBOARD_GRAB], 0, false);
}
static GdkGrabStatus do_pointer_grab(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
GdkGrabStatus status;
status = gdk_pointer_grab(window, FALSE,
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON_MOTION_MASK,
window, d->mouse_cursor,
GDK_CURRENT_TIME);
if (status != GDK_GRAB_SUCCESS) {
d->mouse_grab_active = false;
g_warning("pointer grab failed %d", status);
} else {
d->mouse_grab_active = true;
g_signal_emit(display, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, true);
}
return status;
}
static void update_mouse_pointer(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
if (!window)
return;
switch (d->mouse_mode) {
case SPICE_MOUSE_MODE_CLIENT:
gdk_window_set_cursor(window, d->mouse_cursor);
break;
case SPICE_MOUSE_MODE_SERVER:
if (!d->mouse_grab_active) {
gdk_window_set_cursor(window, NULL);
} else {
gdk_window_set_cursor(window, d->mouse_cursor);
do_pointer_grab(display);
}
break;
default:
break;
}
}
static void try_mouse_grab(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (!d->mouse_grab_enable)
return;
if (d->mouse_mode != SPICE_MOUSE_MODE_SERVER)
return;
if (d->mouse_grab_active)
return;
if (do_pointer_grab(display) != GDK_GRAB_SUCCESS)
return;
d->mouse_last_x = -1;
d->mouse_last_y = -1;
}
static void mouse_check_edges(GtkWidget *widget, GdkEventMotion *motion)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(widget));
GdkScreen *screen = gdk_drawable_get_screen(drawable);
int x = (int)motion->x_root;
int y = (int)motion->y_root;
if (d->mouse_guest_x != -1 && d->mouse_guest_y != -1)
return;
/* In relative mode check to see if client pointer hit
* one of the window edges, and if so move it back by
* 200 pixels. This is important because the pointer
* in the server doesn't correspond 1-for-1, and so
* may still be only half way across the screen. Without
* this warp, the server pointer would thus appear to hit
* an invisible wall */
if (motion->x == 0) x += 100;
if (motion->y == 0) y += 100;
if (motion->x == (d->ww - 1)) x -= 100;
if (motion->y == (d->wh - 1)) y -= 100;
if (x != (int)motion->x_root || y != (int)motion->y_root) {
gdk_display_warp_pointer(gdk_drawable_get_display(drawable),
screen, x, y);
d->mouse_last_x = -1;
d->mouse_last_y = -1;
}
}
static void try_mouse_ungrab(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (!d->mouse_grab_active)
return;
gdk_pointer_ungrab(GDK_CURRENT_TIME);
d->mouse_grab_active = false;
update_mouse_pointer(display);
g_signal_emit(widget, signals[SPICE_DISPLAY_MOUSE_GRAB], 0, false);
}
static void recalc_geometry(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
d->mx = 0;
d->my = 0;
if (d->ww > d->width)
d->mx = (d->ww - d->width) / 2;
if (d->wh > d->height)
d->my = (d->wh - d->height) / 2;
SPICE_DEBUG("%s: guest %dx%d, window %dx%d, offset +%d+%d", __FUNCTION__,
d->width, d->height, d->ww, d->wh, d->mx, d->my);
if (d->resize_guest_enable) {
spice_main_set_display(d->main, d->channel_id,
0, 0, d->ww, d->wh);
}
}
/* ---------------------------------------------------------------- */
static XVisualInfo *get_visual_for_format(GtkWidget *widget, enum SpiceSurfaceFmt format)
{
GdkDrawable *drawable = gtk_widget_get_window(widget);
GdkDisplay *display = gdk_drawable_get_display(drawable);
GdkScreen *screen = gdk_drawable_get_screen(drawable);
XVisualInfo template;
int found, i;
for (i = 0; i < SPICE_N_ELEMENTS(format_table); i++) {
if (format == format_table[i].spice)
break;
}
if (i == SPICE_N_ELEMENTS(format_table))
return NULL;
template = format_table[i].xvisual;
template.screen = gdk_x11_screen_get_screen_number(screen);
return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
VisualScreenMask | VisualDepthMask |
VisualRedMaskMask | VisualGreenMaskMask | VisualBlueMaskMask,
&template, &found);
}
static XVisualInfo *get_visual_default(GtkWidget *widget)
{
GdkDrawable *drawable = gtk_widget_get_window(widget);
GdkDisplay *display = gdk_drawable_get_display(drawable);
GdkScreen *screen = gdk_drawable_get_screen(drawable);
XVisualInfo template;
int found;
template.screen = gdk_x11_screen_get_screen_number(screen);
return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
VisualScreenMask,
&template, &found);
}
static int catch_no_mitshm(Display * dpy, XErrorEvent * event)
{
no_mitshm = true;
return 0;
}
static int ximage_create(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window = gtk_widget_get_window(widget);
GdkDisplay *gtkdpy = gdk_drawable_get_display(window);
void *old_handler = NULL;
XGCValues gcval = {
.foreground = 0,
.background = 0,
};
d->dpy = gdk_x11_display_get_xdisplay(gtkdpy);
d->convert = false;
d->vi = get_visual_for_format(widget, d->format);
if (d->vi == NULL) {
d->convert = true;
d->vi = get_visual_default(widget);
g_return_val_if_fail(d->vi != NULL, 1);
}
if (d->convert) {
g_critical("format conversion not implemented yet");
return 1;
}
d->gc = XCreateGC(d->dpy, gdk_x11_drawable_get_xid(window),
GCForeground | GCBackground, &gcval);
if (d->have_mitshm && d->shmid != -1) {
if (!XShmQueryExtension(d->dpy)) {
goto shm_fail;
}
no_mitshm = false;
old_handler = XSetErrorHandler(catch_no_mitshm);
d->shminfo = spice_new0(XShmSegmentInfo, 1);
d->ximage = XShmCreateImage(d->dpy, d->vi->visual, d->vi->depth,
ZPixmap, d->data, d->shminfo, d->width, d->height);
if (d->ximage == NULL)
goto shm_fail;
d->shminfo->shmaddr = d->data;
d->shminfo->shmid = d->shmid;
d->shminfo->readOnly = false;
XShmAttach(d->dpy, d->shminfo);
XSync(d->dpy, False);
shmctl(d->shmid, IPC_RMID, 0);
if (no_mitshm)
goto shm_fail;
XSetErrorHandler(old_handler);
return 0;
}
shm_fail:
d->have_mitshm = false;
if (old_handler)
XSetErrorHandler(old_handler);
d->ximage = XCreateImage(d->dpy, d->vi->visual, d->vi->depth, ZPixmap, 0,
d->data, d->width, d->height, 32, d->stride);
return 0;
}
static void ximage_destroy(GtkWidget *widget)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->ximage) {
XDestroyImage(d->ximage);
d->ximage = NULL;
}
if (d->shminfo) {
XShmDetach(d->dpy, d->shminfo);
free(d->shminfo);
d->shminfo = NULL;
}
if (d->gc) {
XFreeGC(d->dpy, d->gc);
d->gc = NULL;
}
}
static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window = gtk_widget_get_window(widget);
SPICE_DEBUG("%s: area %dx%d at %d,%d", __FUNCTION__,
expose->area.width,
expose->area.height,
expose->area.x,
expose->area.y);
if (d->mark == 0 || d->data == NULL)
return false;
if (!d->ximage) {
ximage_create(widget);
}
if (expose->area.x >= d->mx &&
expose->area.y >= d->my &&
expose->area.x + expose->area.width <= d->mx + d->width &&
expose->area.y + expose->area.height <= d->my + d->height) {
/* area is completely inside the guest screen -- blit it */
if (d->have_mitshm) {
XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
expose->area.x - d->mx, expose->area.y - d->my,
expose->area.x, expose->area.y,
expose->area.width, expose->area.height,
true);
} else {
XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
expose->area.x - d->mx, expose->area.y - d->my,
expose->area.x, expose->area.y,
expose->area.width, expose->area.height);
}
} else {
/* complete window update */
if (d->ww > d->width || d->wh > d->height) {
int x1 = d->mx;
int x2 = d->mx + d->width;
int y1 = d->my;
int y2 = d->my + d->height;
XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, 0, 0, x1, d->wh);
XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, x2, 0, d->ww - x2, d->wh);
XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, 0, 0, d->ww, y1);
XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, 0, y2, d->ww, d->wh - y2);
}
if (d->have_mitshm) {
XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
0, 0, d->mx, d->my, d->width, d->height,
true);
} else {
XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
0, 0, d->mx, d->my, d->width, d->height);
}
}
return true;
}
/* ---------------------------------------------------------------- */
static void send_key(SpiceDisplay *display, int scancode, int down)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
uint32_t i, b, m;
if (!d->inputs)
return;
i = scancode / 32;
b = scancode % 32;
m = (1 << b);
g_return_if_fail(i < SPICE_N_ELEMENTS(d->key_state));
if (down) {
spice_inputs_key_press(d->inputs, scancode);
d->key_state[i] |= m;
} else {
if (!(d->key_state[i] & m)) {
return;
}
spice_inputs_key_release(d->inputs, scancode);
d->key_state[i] &= ~m;
}
}
static void release_keys(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
uint32_t i, b;
SPICE_DEBUG("%s", __FUNCTION__);
for (i = 0; i < SPICE_N_ELEMENTS(d->key_state); i++) {
if (!d->key_state[i]) {
continue;
}
for (b = 0; b < 32; b++) {
send_key(display, i * 32 + b, 0);
}
}
}
static gboolean check_for_grab_key(SpiceDisplay *display, int type, int keyval)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
int i;
if (!d->grabseq->nkeysyms)
return FALSE;
if (type == GDK_KEY_RELEASE) {
/* Any key release resets the whole grab sequence */
memset(d->activeseq, 0, sizeof(gboolean) * d->grabseq->nkeysyms);
return FALSE;
} else {
/* Record the new key press */
for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
if (d->grabseq->keysyms[i] == keyval)
d->activeseq[i] = TRUE;
/* Return if any key is not pressed */
for (i = 0 ; i < d->grabseq->nkeysyms ; i++)
if (d->activeseq[i] == FALSE)
return FALSE;
return TRUE;
}
}
static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
int scancode;
SPICE_DEBUG("%s %s: keycode: %d state: %d group %d",
__FUNCTION__, key->type == GDK_KEY_PRESS ? "press" : "release",
key->hardware_keycode, key->state, key->group);
if (!d->inputs)
return true;
scancode = vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen,
key->hardware_keycode);
switch (key->type) {
case GDK_KEY_PRESS:
send_key(display, scancode, 1);
break;
case GDK_KEY_RELEASE:
send_key(display, scancode, 0);
break;
default:
break;
}
if (check_for_grab_key(display, key->type, key->keyval)) {
if (d->mouse_grab_active)
try_mouse_ungrab(widget);
else
/* TODO: gtk-vnc has a weird condition here
if (!d->grab_keyboard || !d->absolute) */
try_mouse_grab(widget);
}
return true;
}
static guint get_scancode_from_keyval(SpiceDisplay *display, guint keyval)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
guint keycode = 0;
GdkKeymapKey *keys = NULL;
gint n_keys = 0;
if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
keyval, &keys, &n_keys)) {
/* FIXME what about levels? */
keycode = keys[0].keycode;
g_free(keys);
}
return vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen, keycode);
}
void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
int nkeyvals, SpiceDisplayKeyEvent kind)
{
int i;
g_return_if_fail(SPICE_DISPLAY(display) != NULL);
g_return_if_fail(keyvals != NULL);
SPICE_DEBUG("%s", __FUNCTION__);
if (kind & SPICE_DISPLAY_KEY_EVENT_PRESS) {
for (i = 0 ; i < nkeyvals ; i++)
send_key(display, get_scancode_from_keyval(display, keyvals[i]), 1);
}
if (kind & SPICE_DISPLAY_KEY_EVENT_RELEASE) {
for (i = (nkeyvals-1) ; i >= 0 ; i--)
send_key(display, get_scancode_from_keyval(display, keyvals[i]), 0);
}
}
static gboolean enter_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s", __FUNCTION__);
d->mouse_have_pointer = true;
try_keyboard_grab(widget);
return true;
}
static gboolean leave_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s", __FUNCTION__);
d->mouse_have_pointer = false;
try_keyboard_ungrab(widget);
return true;
}
static gboolean focus_in_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s", __FUNCTION__);
release_keys(display);
spice_sync_keyboard_lock_modifiers(display);
d->keyboard_have_focus = true;
try_keyboard_grab(widget);
return true;
}
static gboolean focus_out_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s", __FUNCTION__);
d->keyboard_have_focus = false;
try_keyboard_ungrab(widget);
return true;
}
static int button_gdk_to_spice(int gdk)
{
static const int map[] = {
[ 1 ] = SPICE_MOUSE_BUTTON_LEFT,
[ 2 ] = SPICE_MOUSE_BUTTON_MIDDLE,
[ 3 ] = SPICE_MOUSE_BUTTON_RIGHT,
[ 4 ] = SPICE_MOUSE_BUTTON_UP,
[ 5 ] = SPICE_MOUSE_BUTTON_DOWN,
};
if (gdk < SPICE_N_ELEMENTS(map)) {
return map [ gdk ];
}
return 0;
}
static int button_mask_gdk_to_spice(int gdk)
{
int spice = 0;
if (gdk & GDK_BUTTON1_MASK)
spice |= SPICE_MOUSE_BUTTON_MASK_LEFT;
if (gdk & GDK_BUTTON2_MASK)
spice |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
if (gdk & GDK_BUTTON3_MASK)
spice |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
return spice;
}
static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (!d->inputs)
return true;
switch (d->mouse_mode) {
case SPICE_MOUSE_MODE_CLIENT:
if (motion->x >= d->mx &&
motion->x < d->mx + d->width &&
motion->y >= d->my &&
motion->y < d->my + d->height) {
spice_inputs_position(d->inputs,
motion->x - d->mx, motion->y - d->my,
d->channel_id,
button_mask_gdk_to_spice(motion->state));
}
break;
case SPICE_MOUSE_MODE_SERVER:
if (d->mouse_grab_active) {
if (d->mouse_last_x != -1 &&
d->mouse_last_y != -1) {
spice_inputs_motion(d->inputs,
motion->x - d->mouse_last_x,
motion->y - d->mouse_last_y,
button_mask_gdk_to_spice(motion->state));
}
d->mouse_last_x = motion->x;
d->mouse_last_y = motion->y;
mouse_check_edges(widget, motion);
}
break;
default:
break;
}
return true;
}
static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *scroll)
{
int button;
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s", __FUNCTION__);
if (!d->inputs)
return true;
if (scroll->direction == GDK_SCROLL_UP)
button = SPICE_MOUSE_BUTTON_UP;
else if (scroll->direction == GDK_SCROLL_DOWN)
button = SPICE_MOUSE_BUTTON_DOWN;
else {
SPICE_DEBUG("unsupported scroll direction");
return true;
}
spice_inputs_button_press(d->inputs, button,
button_mask_gdk_to_spice(scroll->state));
spice_inputs_button_release(d->inputs, button,
button_mask_gdk_to_spice(scroll->state));
return true;
}
static gboolean button_event(GtkWidget *widget, GdkEventButton *button)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
SPICE_DEBUG("%s %s: button %d, state 0x%x", __FUNCTION__,
button->type == GDK_BUTTON_PRESS ? "press" : "release",
button->button, button->state);
gtk_widget_grab_focus(widget);
try_mouse_grab(widget);
if (!d->inputs)
return true;
switch (button->type) {
case GDK_BUTTON_PRESS:
spice_inputs_button_press(d->inputs,
button_gdk_to_spice(button->button),
button_mask_gdk_to_spice(button->state));
break;
case GDK_BUTTON_RELEASE:
spice_inputs_button_release(d->inputs,
button_gdk_to_spice(button->button),
button_mask_gdk_to_spice(button->state));
break;
default:
break;
}
return true;
}
static gboolean configure_event(GtkWidget *widget, GdkEventConfigure *conf)
{
SpiceDisplay *display = SPICE_DISPLAY(widget);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (conf->width != d->ww || conf->height != d->wh) {
d->ww = conf->width;
d->wh = conf->height;
recalc_geometry(widget);
}
return true;
}
/* ---------------------------------------------------------------- */
static const struct {
const char *xatom;
uint32_t vdagent;
uint32_t flags;
} atom2agent[] = {
{
.vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
.xatom = "UTF8_STRING",
},{
.vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
.xatom = "text/plain;charset=utf-8"
},{
.vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
.xatom = "STRING"
},{
.vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
.xatom = "TEXT"
},{
.vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
.xatom = "text/plain"
}
};
static void clipboard_get_targets(GtkClipboard *clipboard,
GdkAtom *atoms,
gint n_atoms,
gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
guint32 types[SPICE_N_ELEMENTS(atom2agent)];
char *name;
int a, m, t;
SPICE_DEBUG("%s:", __FUNCTION__);
if (spice_util_get_debug()) {
for (a = 0; a < n_atoms; a++) {
name = gdk_atom_name(atoms[a]);
SPICE_DEBUG(" \"%s\"", name);
g_free(name);
}
}
memset(types, 0, sizeof(types));
for (a = 0; a < n_atoms; a++) {
name = gdk_atom_name(atoms[a]);
for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
if (strcasecmp(name, atom2agent[m].xatom) != 0) {
continue;
}
/* found match */
for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
if (types[t] == atom2agent[m].vdagent) {
/* type already in list */
break;
}
if (types[t] == 0) {
/* add type to empty slot */
types[t] = atom2agent[m].vdagent;
break;
}
}
break;
}
g_free(name);
}
for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
if (types[t] == 0) {
break;
}
}
if (!d->clip_grabbed && t > 0) {
d->clip_grabbed = true;
spice_main_clipboard_grab(d->main, types, t);
}
}
static void clipboard_owner_change(GtkClipboard *clipboard,
GdkEventOwnerChange *event,
gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->clip_grabbed) {
d->clip_grabbed = false;
spice_main_clipboard_release(d->main);
}
switch (event->reason) {
case GDK_OWNER_CHANGE_NEW_OWNER:
d->clip_hasdata = 1;
if (d->auto_clipboard_enable)
gtk_clipboard_request_targets(clipboard, clipboard_get_targets, data);
break;
default:
d->clip_hasdata = 0;
break;
}
}
/* ---------------------------------------------------------------- */
static void spice_display_class_init(SpiceDisplayClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS(klass);
GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
gtkwidget_class->expose_event = expose_event;
gtkwidget_class->key_press_event = key_event;
gtkwidget_class->key_release_event = key_event;
gtkwidget_class->enter_notify_event = enter_event;
gtkwidget_class->leave_notify_event = leave_event;
gtkwidget_class->focus_in_event = focus_in_event;
gtkwidget_class->focus_out_event = focus_out_event;
gtkwidget_class->motion_notify_event = motion_event;
gtkwidget_class->button_press_event = button_event;
gtkwidget_class->button_release_event = button_event;
gtkwidget_class->configure_event = configure_event;
gtkwidget_class->scroll_event = scroll_event;
gtkobject_class->destroy = spice_display_destroy;
gobject_class->finalize = spice_display_finalize;
gobject_class->get_property = spice_display_get_property;
gobject_class->set_property = spice_display_set_property;
g_object_class_install_property
(gobject_class, PROP_KEYBOARD_GRAB,
g_param_spec_boolean("grab-keyboard",
"Grab Keyboard",
"Whether we should grab the keyboard.",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_MOUSE_GRAB,
g_param_spec_boolean("grab-mouse",
"Grab Mouse",
"Whether we should grab the mouse.",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_RESIZE_GUEST,
g_param_spec_boolean("resize-guest",
"Resize guest",
"Try to adapt guest display on window resize. "
"Requires guest cooperation.",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_AUTO_CLIPBOARD,
g_param_spec_boolean("auto-clipboard",
"Auto clipboard",
"Automatically relay clipboard changes between "
"host and guest.",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
signals[SPICE_DISPLAY_MOUSE_GRAB] =
g_signal_new("mouse-grab",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceDisplayClass, mouse_grab),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE,
1,
G_TYPE_INT);
signals[SPICE_DISPLAY_KEYBOARD_GRAB] =
g_signal_new("keyboard-grab",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceDisplayClass, keyboard_grab),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE,
1,
G_TYPE_INT);
g_type_class_add_private(klass, sizeof(spice_display));
}
/* ---------------------------------------------------------------- */
static void mouse_update(SpiceChannel *channel, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
g_object_get(channel, "mouse-mode", &d->mouse_mode, NULL);
d->mouse_guest_x = -1;
d->mouse_guest_y = -1;
if (d->mouse_mode == SPICE_MOUSE_MODE_CLIENT) {
try_mouse_ungrab(GTK_WIDGET(display));
}
update_mouse_pointer(display);
}
static void primary_create(SpiceChannel *channel, gint format,
gint width, gint height, gint stride,
gint shmid, gpointer imgdata, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
d->format = format;
d->stride = stride;
d->shmid = shmid;
d->data = imgdata;
if (d->width != width || d->height != height) {
d->width = width;
d->height = height;
recalc_geometry(GTK_WIDGET(display));
if (!d->resize_guest_enable) {
gtk_widget_set_size_request(GTK_WIDGET(display), width, height);
}
}
}
static void primary_destroy(SpiceChannel *channel, gpointer data)
{
SpiceDisplay *display = SPICE_DISPLAY(data);
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
d->format = 0;
d->width = 0;
d->height = 0;
d->stride = 0;
d->shmid = 0;
d->data = 0;
ximage_destroy(GTK_WIDGET(display));
}
static void invalidate(SpiceChannel *channel,
gint x, gint y, gint w, gint h, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
gtk_widget_queue_draw_area(GTK_WIDGET(display),
x + d->mx, y + d->my, w, h);
}
static void mark(SpiceChannel *channel, gint mark, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
d->mark = mark;
if (mark != 0 && gtk_widget_get_window(GTK_WIDGET(display)))
gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(display)),
NULL, FALSE);
}
static void cursor_set(SpiceCursorChannel *channel,
gint width, gint height, gint hot_x, gint hot_y,
gpointer rgba, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window;
GdkDisplay *gtkdpy;
GdkPixbuf *pixbuf;
window = gtk_widget_get_window(GTK_WIDGET(display));
if (!window)
return;
gtkdpy = gdk_drawable_get_display(window);
pixbuf = gdk_pixbuf_new_from_data(rgba,
GDK_COLORSPACE_RGB,
TRUE, 8,
width,
height,
width * 4,
NULL, NULL);
if (d->mouse_cursor)
gdk_cursor_unref(d->mouse_cursor);
d->mouse_cursor = gdk_cursor_new_from_pixbuf(gtkdpy, pixbuf,
hot_x, hot_y);
g_object_unref(pixbuf);
update_mouse_pointer(display);
}
static void cursor_hide(SpiceCursorChannel *channel, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *window;
window = gtk_widget_get_window(GTK_WIDGET(display));
if (!window)
return;
if (d->mouse_cursor)
gdk_cursor_unref(d->mouse_cursor);
d->mouse_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
update_mouse_pointer(display);
}
static void cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(GTK_WIDGET(display)));
GdkScreen *screen = gdk_drawable_get_screen(drawable);
int wx, wy;
SPICE_DEBUG("%s: +%d+%d", __FUNCTION__, x, y);
d->mouse_guest_x = x;
d->mouse_guest_y = y;
d->mouse_last_x = x;
d->mouse_last_y = y;
if (d->mouse_grab_active) {
gdk_window_get_origin(drawable, &wx, &wy);
gdk_display_warp_pointer(gdk_drawable_get_display(drawable),
screen, wx + d->mx + x, wy + d->my + y);
}
}
static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
{
SpiceDisplay *display = data;
GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(display));
SPICE_DEBUG("%s", __FUNCTION__);
gdk_window_set_cursor(window, NULL);
}
static void disconnect_main(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->main == NULL)
return;
g_signal_handlers_disconnect_by_func(d->main, G_CALLBACK(mouse_update),
display);
d->main = NULL;
}
static void disconnect_display(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->display == NULL)
return;
g_signal_handlers_disconnect_by_func(d->display, G_CALLBACK(primary_create),
display);
g_signal_handlers_disconnect_by_func(d->display, G_CALLBACK(primary_destroy),
display);
g_signal_handlers_disconnect_by_func(d->display, G_CALLBACK(invalidate),
display);
d->display = NULL;
}
static void disconnect_cursor(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->cursor == NULL)
return;
g_signal_handlers_disconnect_by_func(d->cursor, G_CALLBACK(cursor_set),
display);
g_signal_handlers_disconnect_by_func(d->cursor, G_CALLBACK(cursor_move),
display);
g_signal_handlers_disconnect_by_func(d->cursor, G_CALLBACK(cursor_hide),
display);
g_signal_handlers_disconnect_by_func(d->cursor, G_CALLBACK(cursor_reset),
display);
d->cursor = NULL;
}
typedef struct
{
GMainLoop *loop;
SpiceDisplay *display;
GtkSelectionData *selection_data;
guint info;
gulong timeout_handler;
} RunInfo;
static void clipboard_got_from_guest(SpiceMainChannel *main,
guint type, guchar *data, guint size,
gpointer userdata)
{
RunInfo *ri = userdata;
SPICE_DEBUG("clipboard got data");
gtk_selection_data_set(ri->selection_data,
gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
8, data, size);
if (g_main_loop_is_running (ri->loop))
g_main_loop_quit (ri->loop);
}
static gboolean clipboard_timeout(gpointer data)
{
RunInfo *ri = data;
g_warning("clipboard get timed out");
if (g_main_loop_is_running (ri->loop))
g_main_loop_quit (ri->loop);
ri->timeout_handler = 0;
return FALSE;
}
static void clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data,
guint info, gpointer display)
{
RunInfo ri = { NULL, };
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
gulong clipboard_handler;
SPICE_DEBUG("clipboard get");
g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
ri.display = display;
ri.selection_data = selection_data;
ri.info = info;
ri.loop = g_main_loop_new(NULL, FALSE);
clipboard_handler = g_signal_connect(d->main, "main-clipboard",
G_CALLBACK(clipboard_got_from_guest), &ri);
ri.timeout_handler = g_timeout_add_seconds(7, clipboard_timeout, &ri);
spice_main_clipboard_request(d->main, atom2agent[info].vdagent);
g_main_loop_run(ri.loop);
g_main_loop_unref(ri.loop);
ri.loop = NULL;
g_signal_handler_disconnect(d->main, clipboard_handler);
if (ri.timeout_handler != 0)
g_source_remove(ri.timeout_handler);
}
static void clipboard_clear(GtkClipboard *clipboard, gpointer display)
{
SPICE_DEBUG("clipboard_clear");
// clipboard release ?
}
static gboolean clipboard_grab(SpiceMainChannel *main,
guint32* types, guint32 ntypes, gpointer display)
{
int m, n, i;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
gboolean found;
i = 0;
for (n = 0; n < ntypes; ++n) {
found = FALSE;
for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
found = TRUE;
g_return_val_if_fail(i < SPICE_N_ELEMENTS(atom2agent), FALSE);
targets[i].target = (gchar*)atom2agent[m].xatom;
targets[i].flags = 0;
targets[i].info = m;
target_selected[m] = TRUE;
i += 1;
}
}
if (!found) {
g_warning("clipboard: couldn't find a matching type for: %d", types[n]);
}
}
if (!gtk_clipboard_set_with_data(d->clipboard, targets, i,
clipboard_get, clipboard_clear, display)) {
g_warning("clipboard grab failed");
return FALSE;
}
return TRUE;
}
static void clipboard_received_cb(GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
gint len = 0, m;
guint32 type = VD_AGENT_CLIPBOARD_NONE;
gchar* name;
GdkAtom atom;
len = gtk_selection_data_get_length(selection_data);
if (len == -1) {
SPICE_DEBUG("empty clipboard");
} else if (len == 0) {
SPICE_DEBUG("TODO: what should be done here?");
} else {
atom = gtk_selection_data_get_data_type(selection_data);
name = gdk_atom_name(atom);
for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
if (strcasecmp(name, atom2agent[m].xatom) == 0) {
break;
}
}
if (m >= SPICE_N_ELEMENTS(atom2agent)) {
g_warning("clipboard_received for unsupported type: %s", name);
} else {
type = atom2agent[m].vdagent;
}
g_free(name);
}
spice_main_clipboard_notify(d->main,
type, gtk_selection_data_get_data(selection_data), len);
}
static gboolean clipboard_request(SpiceMainChannel *main,
guint32 type, gpointer display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
int m;
GdkAtom atom;
for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
if (atom2agent[m].vdagent == type)
break;
}
g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
gtk_clipboard_request_contents(d->clipboard, atom,
clipboard_received_cb, display);
return TRUE;
}
static void clipboard_release(SpiceMainChannel *main, gpointer data)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(data);
gtk_clipboard_clear(d->clipboard);
}
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
int id;
g_object_get(channel, "channel-id", &id, NULL);
if (SPICE_IS_MAIN_CHANNEL(channel)) {
d->main = SPICE_MAIN_CHANNEL(channel);
g_signal_connect(channel, "main-mouse-update",
G_CALLBACK(mouse_update), display);
g_signal_connect(channel, "main-clipboard-grab",
G_CALLBACK(clipboard_grab), display);
g_signal_connect(channel, "main-clipboard-request",
G_CALLBACK(clipboard_request), display);
g_signal_connect(channel, "main-clipboard-release",
G_CALLBACK(clipboard_release), display);
mouse_update(channel, display);
return;
}
if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
if (id != d->channel_id)
return;
d->display = channel;
g_signal_connect(channel, "display-primary-create",
G_CALLBACK(primary_create), display);
g_signal_connect(channel, "display-primary-destroy",
G_CALLBACK(primary_destroy), display);
g_signal_connect(channel, "display-invalidate",
G_CALLBACK(invalidate), display);
g_signal_connect(channel, "display-mark",
G_CALLBACK(mark), display);
spice_channel_connect(channel);
return;
}
if (SPICE_IS_CURSOR_CHANNEL(channel)) {
if (id != d->channel_id)
return;
d->cursor = SPICE_CURSOR_CHANNEL(channel);
g_signal_connect(channel, "cursor-set",
G_CALLBACK(cursor_set), display);
g_signal_connect(channel, "cursor-move",
G_CALLBACK(cursor_move), display);
g_signal_connect(channel, "cursor-hide",
G_CALLBACK(cursor_hide), display);
g_signal_connect(channel, "cursor-reset",
G_CALLBACK(cursor_reset), display);
spice_channel_connect(channel);
return;
}
if (SPICE_IS_INPUTS_CHANNEL(channel)) {
d->inputs = SPICE_INPUTS_CHANNEL(channel);
spice_channel_connect(channel);
spice_sync_keyboard_lock_modifiers(display);
return;
}
return;
}
static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
SpiceDisplay *display = data;
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
int id;
g_object_get(channel, "channel-id", &id, NULL);
if (SPICE_IS_MAIN_CHANNEL(channel)) {
disconnect_main(display);
return;
}
if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
if (id != d->channel_id)
return;
disconnect_display(display);
return;
}
if (SPICE_IS_CURSOR_CHANNEL(channel)) {
if (id != d->channel_id)
return;
disconnect_cursor(display);
return;
}
if (SPICE_IS_INPUTS_CHANNEL(channel)) {
release_keys(display);
d->inputs = NULL;
return;
}
return;
}
SpiceDisplay *spice_display_new(SpiceSession *session, int id)
{
SpiceDisplay *display;
spice_display *d;
GList *list;
display = g_object_new(SPICE_TYPE_DISPLAY, NULL);
d = SPICE_DISPLAY_GET_PRIVATE(display);
d->session = session;
d->channel_id = id;
g_signal_connect(session, "channel-new",
G_CALLBACK(channel_new), display);
g_signal_connect(session, "channel-destroy",
G_CALLBACK(channel_destroy), display);
list = spice_session_get_channels(session);
for (list = g_list_first(list); list != NULL; list = g_list_next(list)) {
channel_new(session, list->data, (gpointer*)display);
}
g_list_free(list);
return display;
}
void spice_display_mouse_ungrab(SpiceDisplay *display)
{
try_mouse_ungrab(GTK_WIDGET(display));
}
void spice_display_copy_to_guest(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
if (d->clip_hasdata && !d->clip_grabbed) {
gtk_clipboard_request_targets(d->clipboard, clipboard_get_targets, display);
}
}
void spice_display_paste_from_guest(SpiceDisplay *display)
{
g_warning("%s: TODO", __FUNCTION__);
}
GdkPixbuf *spice_display_get_pixbuf(SpiceDisplay *display)
{
spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
GdkPixbuf *pixbuf;
int x, y;
guchar *src, *data, *dest;
g_return_val_if_fail(d != NULL, NULL);
g_return_val_if_fail(d->format == SPICE_SURFACE_FMT_32_xRGB, NULL);
data = g_malloc(d->width * d->height * 3);
src = d->data;
dest = data;
for (y = 0; y < d->height; ++y) {
for (x = 0; x < d->width; ++x) {
dest[0] = src[x * 4 + 2];
dest[1] = src[x * 4 + 1];
dest[2] = src[x * 4 + 0];
dest += 3;
}
src += d->stride;
}
pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false,
8, d->width, d->height, d->width * 3,
(GdkPixbufDestroyNotify)g_free, NULL);
return pixbuf;
}