/* -*- 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 "config.h"
#include "spice-widget.h"
#include "spice-widget-priv.h"
#ifdef HAVE_SYS_SHM_H
#include
#endif
#ifdef HAVE_SYS_IPC_H
#include
#endif
static bool no_mitshm;
static struct format_table {
enum SpiceSurfaceFmt spice;
XVisualInfo xvisual;
} format_table[] = {
{
.spice = SPICE_SURFACE_FMT_32_ARGB, /* FIXME: is that correct xvisual? */
.xvisual = {
.depth = 24,
.red_mask = 0xff0000,
.green_mask = 0x00ff00,
.blue_mask = 0x0000ff,
},
},{
.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 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;
XVisualInfo *vi;
for (i = 0; i < SPICE_N_ELEMENTS(format_table); i++) {
if (format == format_table[i].spice)
break;
}
if (i == SPICE_N_ELEMENTS(format_table)) {
g_warn_if_reached();
return NULL;
}
template = format_table[i].xvisual;
template.screen = gdk_x11_screen_get_screen_number(screen);
vi = XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
VisualScreenMask | VisualDepthMask |
VisualRedMaskMask | VisualGreenMaskMask | VisualBlueMaskMask,
&template, &found);
return vi;
}
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;
}
G_GNUC_INTERNAL
int spicex_image_create(SpiceDisplay *display)
{
SpiceDisplayPrivate *d = display->priv;
if (d->ximage != NULL)
return 0;
GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
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(GTK_WIDGET(display), d->format);
if (d->vi == NULL) {
d->convert = true;
d->vi = get_visual_default(GTK_WIDGET(display));
d->vi = get_visual_for_format(GTK_WIDGET(display), SPICE_SURFACE_FMT_32_xRGB);
g_return_val_if_fail(d->vi != NULL, 1);
}
if (d->convert) {
d->data = g_malloc0(d->height * d->stride); /* pixels are 32 bits */
}
d->gc = XCreateGC(d->dpy, gdk_x11_drawable_get_xid(window),
GCForeground | GCBackground, &gcval);
if (d->convert) /* do not use shm when doing color format conversion */
goto xcreate;
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 = g_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;
g_free(d->shminfo);
d->shminfo = NULL;
if (old_handler)
XSetErrorHandler(old_handler);
xcreate:
d->ximage = XCreateImage(d->dpy, d->vi->visual, d->vi->depth, ZPixmap, 0,
d->data, d->width, d->height, 32, d->stride);
return 0;
}
G_GNUC_INTERNAL
void spicex_image_destroy(SpiceDisplay *display)
{
SpiceDisplayPrivate *d = display->priv;
if (d->ximage) {
/* avoid XDestroy to free shared memory, owned and freed by
channel-display itself */
if (d->ximage->data == d->data_origin)
d->ximage->data = NULL;
XDestroyImage(d->ximage);
d->ximage = NULL;
if (d->convert)
d->data = 0;
}
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;
}
if (d->convert && d->data) {
g_free(d->data);
d->data = NULL;
}
}
G_GNUC_INTERNAL
void spicex_expose_event(SpiceDisplay *display, GdkEventExpose *expose)
{
GdkDrawable *window = gtk_widget_get_window(GTK_WIDGET(display));
SpiceDisplayPrivate *d = display->priv;
int x, y, w, h;
spice_display_get_scaling(display, NULL, &x, &y, &w, &h);
if (expose->area.x >= x &&
expose->area.y >= y &&
expose->area.x + expose->area.width <= x + w &&
expose->area.y + expose->area.height <= y + h) {
/* area is completely inside the guest screen -- blit it */
if (d->have_mitshm && d->shminfo) {
XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
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,
d->area.x + expose->area.x - x, d->area.y + expose->area.y - y,
expose->area.x, expose->area.y,
expose->area.width, expose->area.height);
}
} else {
/* complete window update */
if (d->ww > d->area.width || d->wh > d->area.height) {
int x1 = x;
int x2 = x + w;
int y1 = y;
int y2 = y + h;
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 && d->shminfo) {
XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
d->area.x, d->area.y, x, y, w, h,
true);
} else {
XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
d->gc, d->ximage,
d->area.x, d->area.y, x, y, w, h);
}
}
}
G_GNUC_INTERNAL
gboolean spicex_is_scaled(SpiceDisplay *display)
{
return FALSE; /* backend doesn't support scaling yet */
}