2023-02-09 12:38:48 +00:00
|
|
|
/*
|
|
|
|
* mks-paintable.c
|
|
|
|
*
|
|
|
|
* Copyright 2023 Christian Hergert <chergert@redhat.com>
|
|
|
|
*
|
|
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#include <glib/gstdio.h>
|
2023-02-11 01:32:09 +00:00
|
|
|
#include <pixman.h>
|
2023-02-11 00:14:51 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
#include "mks-cairo-framebuffer-private.h"
|
2023-02-10 01:32:00 +00:00
|
|
|
#include "mks-paintable-private.h"
|
2023-02-11 00:14:51 +00:00
|
|
|
#include "mks-qemu.h"
|
2023-02-09 12:38:48 +00:00
|
|
|
|
|
|
|
struct _MksPaintable
|
|
|
|
{
|
2023-02-14 01:38:30 +00:00
|
|
|
GObject parent_instance;
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
MksQemuListener *listener;
|
|
|
|
GDBusConnection *connection;
|
|
|
|
MksCairoFramebuffer *framebuffer;
|
2023-02-12 18:39:14 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
guint mode : 2;
|
2023-02-12 18:39:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
MODE_INITIAL = 0,
|
|
|
|
MODE_FRAMEBUFFER,
|
|
|
|
MODE_DMABUF,
|
2023-02-09 12:38:48 +00:00
|
|
|
};
|
|
|
|
|
2023-02-11 01:32:09 +00:00
|
|
|
static cairo_format_t
|
|
|
|
_pixman_format_to_cairo_format (guint pixman_format)
|
|
|
|
{
|
|
|
|
switch (pixman_format)
|
|
|
|
{
|
|
|
|
case PIXMAN_rgba_float:
|
|
|
|
return CAIRO_FORMAT_RGBA128F;
|
|
|
|
case PIXMAN_rgb_float:
|
|
|
|
return CAIRO_FORMAT_RGB96F;
|
|
|
|
case PIXMAN_a8r8g8b8:
|
|
|
|
return CAIRO_FORMAT_ARGB32;
|
|
|
|
case PIXMAN_x2r10g10b10:
|
|
|
|
return CAIRO_FORMAT_RGB30;
|
|
|
|
case PIXMAN_x8r8g8b8:
|
|
|
|
return CAIRO_FORMAT_RGB24;
|
|
|
|
case PIXMAN_a8:
|
|
|
|
return CAIRO_FORMAT_A8;
|
|
|
|
case PIXMAN_a1:
|
|
|
|
return CAIRO_FORMAT_A1;
|
|
|
|
case PIXMAN_r5g6b5:
|
|
|
|
return CAIRO_FORMAT_RGB16_565;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static int
|
|
|
|
mks_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-14 01:38:30 +00:00
|
|
|
MksPaintable *self = MKS_PAINTABLE (paintable);
|
|
|
|
|
|
|
|
if (self->mode == MODE_FRAMEBUFFER)
|
|
|
|
return gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->framebuffer));
|
|
|
|
|
|
|
|
return 0;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static int
|
|
|
|
mks_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-14 01:38:30 +00:00
|
|
|
MksPaintable *self = MKS_PAINTABLE (paintable);
|
|
|
|
|
|
|
|
if (self->mode == MODE_FRAMEBUFFER)
|
|
|
|
return gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->framebuffer));
|
|
|
|
|
|
|
|
return 0;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static double
|
|
|
|
mks_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
MksPaintable *self = MKS_PAINTABLE (paintable);
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (self->mode == MODE_FRAMEBUFFER)
|
|
|
|
return gdk_paintable_get_intrinsic_aspect_ratio (GDK_PAINTABLE (self->framebuffer));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
return .0;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2023-02-11 00:14:51 +00:00
|
|
|
mks_paintable_snapshot (GdkPaintable *paintable,
|
|
|
|
GdkSnapshot *snapshot,
|
|
|
|
double width,
|
|
|
|
double height)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-12 18:39:14 +00:00
|
|
|
MksPaintable *self = (MksPaintable *)paintable;
|
|
|
|
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (GDK_IS_SNAPSHOT (snapshot));
|
|
|
|
|
|
|
|
if (self->mode == MODE_FRAMEBUFFER)
|
2023-02-14 01:38:30 +00:00
|
|
|
gdk_paintable_snapshot (GDK_PAINTABLE (self->framebuffer), snapshot, width, height);
|
2023-02-11 00:14:51 +00:00
|
|
|
}
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static void
|
|
|
|
paintable_iface_init (GdkPaintableInterface *iface)
|
|
|
|
{
|
|
|
|
iface->get_intrinsic_height = mks_paintable_get_intrinsic_height;
|
|
|
|
iface->get_intrinsic_width = mks_paintable_get_intrinsic_width;
|
|
|
|
iface->get_intrinsic_aspect_ratio = mks_paintable_get_intrinsic_aspect_ratio;
|
|
|
|
iface->snapshot = mks_paintable_snapshot;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
G_DEFINE_FINAL_TYPE_WITH_CODE (MksPaintable, mks_paintable, G_TYPE_OBJECT,
|
|
|
|
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, paintable_iface_init))
|
|
|
|
|
2023-02-09 12:38:48 +00:00
|
|
|
static void
|
2023-02-11 00:14:51 +00:00
|
|
|
mks_paintable_dispose (GObject *object)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
MksPaintable *self = (MksPaintable *)object;
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
g_clear_object (&self->connection);
|
|
|
|
g_clear_object (&self->listener);
|
2023-02-14 01:38:30 +00:00
|
|
|
g_clear_object (&self->framebuffer);
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
G_OBJECT_CLASS (mks_paintable_parent_class)->dispose (object);
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mks_paintable_class_init (MksPaintableClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
|
|
|
object_class->dispose = mks_paintable_dispose;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mks_paintable_init (MksPaintable *self)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-14 19:59:04 +00:00
|
|
|
static void
|
|
|
|
mks_paintable_invalidate_contents_cb (MksPaintable *self,
|
|
|
|
GdkPaintable *paintable)
|
|
|
|
{
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (GDK_IS_PAINTABLE (paintable));
|
|
|
|
|
|
|
|
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mks_paintable_invalidate_size_cb (MksPaintable *self,
|
|
|
|
GdkPaintable *paintable)
|
|
|
|
{
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (GDK_IS_PAINTABLE (paintable));
|
|
|
|
|
|
|
|
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
|
|
|
|
}
|
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
static void
|
|
|
|
mks_paintable_set_framebuffer (MksPaintable *self,
|
|
|
|
MksCairoFramebuffer *framebuffer)
|
|
|
|
{
|
|
|
|
gboolean size_changed;
|
|
|
|
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (!framebuffer || MKS_IS_CAIRO_FRAMEBUFFER (framebuffer));
|
|
|
|
|
|
|
|
if (self->framebuffer == framebuffer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
size_changed = self->framebuffer == NULL ||
|
|
|
|
framebuffer == NULL ||
|
|
|
|
mks_cairo_framebuffer_get_width (self->framebuffer) != mks_cairo_framebuffer_get_width (framebuffer) ||
|
|
|
|
mks_cairo_framebuffer_get_height (self->framebuffer) != mks_cairo_framebuffer_get_height (framebuffer);
|
|
|
|
|
|
|
|
if (self->framebuffer != NULL)
|
|
|
|
{
|
|
|
|
g_signal_handlers_disconnect_by_func (self->framebuffer,
|
|
|
|
G_CALLBACK (gdk_paintable_invalidate_size),
|
|
|
|
self);
|
|
|
|
g_signal_handlers_disconnect_by_func (self->framebuffer,
|
|
|
|
G_CALLBACK (gdk_paintable_invalidate_contents),
|
|
|
|
self);
|
|
|
|
g_clear_object (&self->framebuffer);
|
|
|
|
self->mode = MODE_INITIAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (framebuffer != NULL)
|
|
|
|
{
|
|
|
|
self->framebuffer = g_object_ref (framebuffer);
|
|
|
|
g_signal_connect_object (self->framebuffer,
|
|
|
|
"invalidate-size",
|
2023-02-14 19:59:04 +00:00
|
|
|
G_CALLBACK (mks_paintable_invalidate_size_cb),
|
2023-02-14 01:38:30 +00:00
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
g_signal_connect_object (self->framebuffer,
|
|
|
|
"invalidate-contents",
|
2023-02-14 19:59:04 +00:00
|
|
|
G_CALLBACK (mks_paintable_invalidate_contents_cb),
|
2023-02-14 01:38:30 +00:00
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
self->mode = MODE_FRAMEBUFFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
|
|
|
|
|
|
|
if (size_changed)
|
|
|
|
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
|
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_update_dmabuf (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 01:21:33 +00:00
|
|
|
mks_qemu_listener_complete_update_dmabuf (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_scanout_dmabuf (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
GUnixFDList *unix_fd_list,
|
|
|
|
GVariant *dmabuf,
|
|
|
|
guint width,
|
|
|
|
guint height,
|
|
|
|
guint stride,
|
|
|
|
guint fourcc,
|
|
|
|
guint64 modifier,
|
|
|
|
gboolean y0_top,
|
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 01:21:33 +00:00
|
|
|
mks_qemu_listener_complete_scanout_dmabuf (listener, invocation, NULL);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_update (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
guint stride,
|
|
|
|
guint pixman_format,
|
2023-02-14 20:24:15 +00:00
|
|
|
GVariant *bytestring,
|
2023-02-11 00:14:51 +00:00
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-14 20:24:15 +00:00
|
|
|
g_autoptr(GBytes) bytes = NULL;
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_surface_t *source;
|
2023-02-14 20:24:15 +00:00
|
|
|
const guint8 *data;
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_t *cr;
|
2023-02-11 17:19:29 +00:00
|
|
|
cairo_format_t format;
|
2023-02-14 01:38:30 +00:00
|
|
|
gsize data_len;
|
2023-02-11 17:19:29 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (self->mode != MODE_FRAMEBUFFER ||
|
|
|
|
!(format = _pixman_format_to_cairo_format (pixman_format)))
|
2023-02-11 17:19:29 +00:00
|
|
|
{
|
|
|
|
g_dbus_method_invocation_return_error_literal (invocation,
|
|
|
|
G_IO_ERROR,
|
|
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
2023-02-14 01:38:30 +00:00
|
|
|
"Invalid operation");
|
2023-02-11 17:19:29 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2023-02-14 20:24:15 +00:00
|
|
|
bytes = g_variant_get_data_as_bytes (bytestring);
|
|
|
|
data = g_bytes_get_data (bytes, &data_len);
|
2023-02-12 18:39:14 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (data_len < cairo_format_stride_for_width (format, width) * height)
|
|
|
|
{
|
|
|
|
g_dbus_method_invocation_return_error_literal (invocation,
|
|
|
|
G_IO_ERROR,
|
|
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
|
|
"Stride invalid for size");
|
|
|
|
return TRUE;
|
|
|
|
}
|
2023-02-12 18:39:14 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
/* We can get in a protocol race condition here in that we will get updates
|
|
|
|
* for framebuffer content _BEFORE_ we'll get notified of property changes
|
|
|
|
* about the MksQemuConsole's size.
|
|
|
|
*
|
|
|
|
* To overcome that, if we detect something larger than our current
|
|
|
|
* framebuffer, we'll resize it and draw over the old contents in a
|
|
|
|
* new framebuffer.
|
|
|
|
*
|
|
|
|
* When shrinking, we can do this as well and then handle it when the
|
|
|
|
* console size notification arrives.
|
|
|
|
*
|
|
|
|
* Generally this is seen at startup during EFI/BIOS.
|
|
|
|
*/
|
|
|
|
if (x + width > mks_cairo_framebuffer_get_width (self->framebuffer) ||
|
|
|
|
y + height > mks_cairo_framebuffer_get_height (self->framebuffer))
|
|
|
|
{
|
|
|
|
guint max_width = MAX (mks_cairo_framebuffer_get_width (self->framebuffer), x + width);
|
|
|
|
guint max_height = MAX (mks_cairo_framebuffer_get_height (self->framebuffer), y + height);
|
|
|
|
g_autoptr(MksCairoFramebuffer) framebuffer = mks_cairo_framebuffer_new (format, max_width, max_height);
|
2023-02-12 18:39:14 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
mks_cairo_framebuffer_copy_to (self->framebuffer, framebuffer);
|
|
|
|
mks_paintable_set_framebuffer (self, framebuffer);
|
2023-02-12 18:39:14 +00:00
|
|
|
}
|
2023-02-11 01:21:33 +00:00
|
|
|
|
2023-02-14 20:24:15 +00:00
|
|
|
source = cairo_image_surface_create_for_data ((guint8 *)data, format, width, height, stride);
|
2023-02-14 01:38:30 +00:00
|
|
|
cr = mks_cairo_framebuffer_update (self->framebuffer, x, y, width, height);
|
2023-02-14 19:59:04 +00:00
|
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_set_source_surface (cr, source, 0, 0);
|
2023-02-14 19:59:04 +00:00
|
|
|
cairo_rectangle (cr, 0, 0, width, height);
|
2023-02-14 20:24:15 +00:00
|
|
|
cairo_paint (cr);
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_destroy (cr);
|
|
|
|
cairo_surface_destroy (source);
|
|
|
|
|
2023-02-11 01:12:07 +00:00
|
|
|
mks_qemu_listener_complete_update (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_scanout (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
guint width,
|
|
|
|
guint height,
|
|
|
|
guint stride,
|
|
|
|
guint pixman_format,
|
2023-02-14 20:24:15 +00:00
|
|
|
GVariant *bytestring,
|
2023-02-11 00:14:51 +00:00
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-14 20:24:15 +00:00
|
|
|
g_autoptr(GBytes) bytes = NULL;
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_surface_t *source;
|
2023-02-14 20:24:15 +00:00
|
|
|
const guint8 *data;
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_t *cr;
|
2023-02-11 17:19:29 +00:00
|
|
|
cairo_format_t format;
|
2023-02-12 18:39:14 +00:00
|
|
|
gsize data_len;
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
2023-02-14 20:24:15 +00:00
|
|
|
g_assert (g_variant_is_of_type (bytestring, G_VARIANT_TYPE_BYTESTRING));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 17:19:29 +00:00
|
|
|
if (!(format = _pixman_format_to_cairo_format (pixman_format)))
|
|
|
|
{
|
|
|
|
g_dbus_method_invocation_return_error_literal (invocation,
|
|
|
|
G_IO_ERROR,
|
|
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
|
|
"Pixman format not supported");
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2023-02-14 20:24:15 +00:00
|
|
|
bytes = g_variant_get_data_as_bytes (bytestring);
|
|
|
|
data = g_bytes_get_data (bytes, &data_len);
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (data_len < cairo_format_stride_for_width (format, width) * height)
|
2023-02-12 18:39:14 +00:00
|
|
|
{
|
2023-02-14 01:38:30 +00:00
|
|
|
g_dbus_method_invocation_return_error_literal (invocation,
|
|
|
|
G_IO_ERROR,
|
|
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
|
|
"Stride invalid for size");
|
|
|
|
return TRUE;
|
2023-02-12 18:39:14 +00:00
|
|
|
}
|
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (self->framebuffer == NULL ||
|
|
|
|
width != mks_cairo_framebuffer_get_width (self->framebuffer) ||
|
|
|
|
height != mks_cairo_framebuffer_get_height (self->framebuffer))
|
|
|
|
{
|
|
|
|
g_autoptr(MksCairoFramebuffer) framebuffer = mks_cairo_framebuffer_new (format, width, height);
|
2023-02-12 18:39:14 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
mks_paintable_set_framebuffer (self, framebuffer);
|
|
|
|
}
|
2023-02-11 01:12:07 +00:00
|
|
|
|
2023-02-14 20:24:15 +00:00
|
|
|
source = cairo_image_surface_create_for_data ((guint8 *)data, format, width, height, stride);
|
2023-02-14 01:38:30 +00:00
|
|
|
cr = mks_cairo_framebuffer_update (self->framebuffer, 0, 0, width, height);
|
|
|
|
cairo_set_source_surface (cr, source, 0, 0);
|
2023-02-14 19:59:04 +00:00
|
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
2023-02-14 01:38:30 +00:00
|
|
|
cairo_rectangle (cr, 0, 0, width, height);
|
|
|
|
cairo_paint (cr);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
cairo_surface_destroy (source);
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 01:12:07 +00:00
|
|
|
mks_qemu_listener_complete_scanout (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_cursor_define (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
int hot_x,
|
|
|
|
int hot_y,
|
|
|
|
GVariant *bytes,
|
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
|
|
|
|
2023-02-11 01:21:33 +00:00
|
|
|
mks_qemu_listener_complete_cursor_define (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_mouse_set (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int on,
|
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
2023-02-09 12:38:48 +00:00
|
|
|
|
2023-02-11 01:21:33 +00:00
|
|
|
mks_qemu_listener_complete_mouse_set (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
mks_paintable_listener_disable (MksPaintable *self,
|
|
|
|
GDBusMethodInvocation *invocation,
|
|
|
|
MksQemuListener *listener)
|
2023-02-09 12:38:48 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (listener));
|
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
if (self->mode == MODE_FRAMEBUFFER)
|
|
|
|
{
|
|
|
|
if (self->framebuffer != NULL)
|
|
|
|
mks_cairo_framebuffer_clear (self->framebuffer);
|
|
|
|
}
|
2023-02-11 01:21:33 +00:00
|
|
|
|
2023-02-14 01:38:30 +00:00
|
|
|
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
2023-02-11 01:12:07 +00:00
|
|
|
|
2023-02-11 01:21:33 +00:00
|
|
|
mks_qemu_listener_complete_disable (listener, invocation);
|
|
|
|
|
|
|
|
return TRUE;
|
2023-02-09 12:38:48 +00:00
|
|
|
}
|
2023-02-10 01:32:00 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
static gboolean
|
|
|
|
create_socketpair (int *us,
|
|
|
|
int *them,
|
|
|
|
GError **error)
|
2023-02-10 01:32:00 +00:00
|
|
|
{
|
2023-02-11 00:14:51 +00:00
|
|
|
int fds[2];
|
|
|
|
int rv;
|
|
|
|
|
2023-02-11 00:29:38 +00:00
|
|
|
rv = socketpair (AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds);
|
2023-02-11 00:14:51 +00:00
|
|
|
|
|
|
|
if (rv != 0)
|
|
|
|
{
|
|
|
|
int errsv = errno;
|
|
|
|
g_set_error_literal (error,
|
|
|
|
G_IO_ERROR,
|
|
|
|
g_io_error_from_errno (errsv),
|
|
|
|
g_strerror (errsv));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
*us = fds[0];
|
|
|
|
*them = fds[1];
|
2023-02-10 01:32:00 +00:00
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
2023-02-10 01:32:00 +00:00
|
|
|
|
2023-02-11 01:12:07 +00:00
|
|
|
static void
|
|
|
|
mks_paintable_connection_cb (GObject *object,
|
|
|
|
GAsyncResult *result,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
g_autoptr(MksPaintable) self = user_data;
|
|
|
|
g_autoptr(GDBusConnection) connection = NULL;
|
|
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
|
|
|
|
|
|
if (!(connection = g_dbus_connection_new_finish (result, &error)))
|
|
|
|
{
|
|
|
|
g_warning ("Failed to create D-Bus connection: %s", error->message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_set_object (&self->connection, connection);
|
|
|
|
|
|
|
|
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->listener),
|
|
|
|
connection,
|
|
|
|
"/org/qemu/Display1/Listener",
|
|
|
|
&error))
|
|
|
|
{
|
|
|
|
g_warning ("Failed to export listener on bus: %s", error->message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_dbus_connection_start_message_processing (connection);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
GdkPaintable *
|
|
|
|
_mks_paintable_new (GCancellable *cancellable,
|
|
|
|
int *peer_fd,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
g_autoptr(MksPaintable) self = NULL;
|
|
|
|
g_autoptr(GSocketConnection) io_stream = NULL;
|
|
|
|
g_autoptr(GSocket) socket = NULL;
|
|
|
|
g_autofd int us = -1;
|
|
|
|
g_autofd int them = -1;
|
|
|
|
|
|
|
|
g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
|
|
|
|
g_return_val_if_fail (peer_fd != NULL, NULL);
|
|
|
|
|
|
|
|
*peer_fd = -1;
|
|
|
|
|
|
|
|
self = g_object_new (MKS_TYPE_PAINTABLE, NULL);
|
|
|
|
|
|
|
|
/* Create a socketpair() to use for D-Bus P2P protocol. We will be receiving
|
|
|
|
* DMA-BUF FDs over this.
|
|
|
|
*/
|
|
|
|
if (!create_socketpair (&us, &them, error))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Create socket for our side of the socket pair */
|
|
|
|
if (!(socket = g_socket_new_from_fd (us, error)))
|
|
|
|
return NULL;
|
|
|
|
us = -1;
|
|
|
|
|
|
|
|
/* And convert that socket into a GIOStream */
|
|
|
|
io_stream = g_socket_connection_factory_create_connection (socket);
|
|
|
|
|
|
|
|
/* Setup our listener and callbacks to process requests */
|
2023-02-11 01:12:07 +00:00
|
|
|
self->listener = mks_qemu_listener_skeleton_new ();
|
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-scanout",
|
|
|
|
G_CALLBACK (mks_paintable_listener_scanout),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-update",
|
|
|
|
G_CALLBACK (mks_paintable_listener_update),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-scanout-dmabuf",
|
|
|
|
G_CALLBACK (mks_paintable_listener_scanout_dmabuf),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-update-dmabuf",
|
|
|
|
G_CALLBACK (mks_paintable_listener_update_dmabuf),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-disable",
|
|
|
|
G_CALLBACK (mks_paintable_listener_disable),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-cursor-define",
|
|
|
|
G_CALLBACK (mks_paintable_listener_cursor_define),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
2023-02-11 01:12:07 +00:00
|
|
|
g_signal_connect_object (self->listener,
|
2023-02-11 00:14:51 +00:00
|
|
|
"handle-mouse-set",
|
|
|
|
G_CALLBACK (mks_paintable_listener_mouse_set),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
2023-02-11 01:12:07 +00:00
|
|
|
/* Asynchronously create connection because we can't do it synchronously
|
|
|
|
* as the other side is doing AUTHENTICATION_SERVER for no good reason.
|
2023-02-11 00:14:51 +00:00
|
|
|
*/
|
2023-02-11 01:12:07 +00:00
|
|
|
g_dbus_connection_new (G_IO_STREAM (io_stream),
|
|
|
|
NULL,
|
|
|
|
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING|G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
|
|
|
|
NULL,
|
|
|
|
cancellable,
|
|
|
|
mks_paintable_connection_cb,
|
|
|
|
g_object_ref (self));
|
2023-02-11 00:14:51 +00:00
|
|
|
|
|
|
|
*peer_fd = g_steal_fd (&them);
|
|
|
|
|
2023-02-11 01:12:07 +00:00
|
|
|
g_assert (*peer_fd != -1);
|
|
|
|
g_assert (MKS_IS_PAINTABLE (self));
|
|
|
|
g_assert (MKS_QEMU_IS_LISTENER (self->listener));
|
|
|
|
|
2023-02-11 00:14:51 +00:00
|
|
|
return GDK_PAINTABLE (g_steal_pointer (&self));
|
2023-02-10 01:32:00 +00:00
|
|
|
}
|