lib: add mks_screen_attach()

This is ultimately going to give us back a paintable, but it's only in
partial state right now. We need to figure out the right semantics for
ownership between the listener/paintable/screen/etc.

We may want the widget to be the owner of everything (and keep the
painable/listener internal API) which is likely the most convenient from
an object ownership standpoint.
This commit is contained in:
Christian Hergert 2023-02-09 17:32:00 -08:00
parent 5e75216318
commit 629cfd0e28
4 changed files with 292 additions and 1 deletions

View File

@ -0,0 +1,33 @@
/*
* mks-paintable-private.h
*
* 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
*/
#pragma once
#include "mks-paintable.h"
#include "mks-paintable-listener-private.h"
G_BEGIN_DECLS
GdkPaintable *_mks_paintable_new (GDBusConnection *connection,
MksScreen *screen,
MksPaintableListener *listener);
G_END_DECLS

View File

@ -21,7 +21,7 @@
#include "config.h" #include "config.h"
#include "mks-paintable.h" #include "mks-paintable-private.h"
#include "mks-screen.h" #include "mks-screen.h"
struct _MksPaintable struct _MksPaintable
@ -224,3 +224,17 @@ paintable_iface_init (GdkPaintableInterface *iface)
iface->get_intrinsic_aspect_ratio = mks_paintable_get_intrinsic_aspect_ratio; iface->get_intrinsic_aspect_ratio = mks_paintable_get_intrinsic_aspect_ratio;
iface->snapshot = mks_paintable_snapshot; iface->snapshot = mks_paintable_snapshot;
} }
GdkPaintable *
_mks_paintable_new (GDBusConnection *connection,
MksScreen *screen,
MksPaintableListener *listener)
{
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
g_return_val_if_fail (MKS_IS_SCREEN (screen), NULL);
g_return_val_if_fail (MKS_IS_PAINTABLE_LISTENER (listener), NULL);
/* TODO: */
return mks_paintable_new (screen);
}

View File

@ -21,11 +21,18 @@
#include "config.h" #include "config.h"
#include <errno.h>
#include <sys/socket.h>
#include <glib/gstdio.h>
#include "mks-device-private.h" #include "mks-device-private.h"
#include "mks-enums.h" #include "mks-enums.h"
#include "mks-qemu.h" #include "mks-qemu.h"
#include "mks-keyboard-private.h" #include "mks-keyboard-private.h"
#include "mks-mouse-private.h" #include "mks-mouse-private.h"
#include "mks-paintable-listener-private.h"
#include "mks-paintable-private.h"
#include "mks-screen-attributes-private.h" #include "mks-screen-attributes-private.h"
#include "mks-screen-private.h" #include "mks-screen-private.h"
@ -556,3 +563,226 @@ mks_screen_configure_sync (MksScreen *self,
cancellable, cancellable,
error); error);
} }
static gboolean
mks_screen_create_socketpair (int *us,
int *them,
GError **error)
{
int rv;
int fds[2];
g_assert (us != NULL);
g_assert (them != NULL);
rv = socketpair (AF_UNIX, SOCK_NONBLOCK | SOCK_CLOEXEC, SOCK_STREAM, fds);
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];
return TRUE;
}
static void
mks_screen_attach_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
MksQemuConsole *console = (MksQemuConsole *)object;
g_autoptr(GTask) task = user_data;
g_autoptr(GError) error = NULL;
g_assert (G_IS_OBJECT (object));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (G_IS_TASK (task));
if (!mks_qemu_console_call_register_listener_finish (console, NULL, result, &error))
g_task_return_error (task, g_steal_pointer (&error));
else
g_task_return_pointer (task,
g_object_ref (g_task_get_task_data (task)),
g_object_unref);
}
/**
* mks_screen_attach:
* @self: an #MksScreen
* @cancellable: (nullable): a #GCancellable
* @callback: a #GAsyncReadyCallback to execute upon completion
* @user_data: closure data for @callback
*
* Asynchronously creates a #GdkPaintable that is updated with the
* contents of the screen.
*
* This function registers a new `socketpair()` which is shared with
* the Qemu instance to receive rendering updates. Those updates are
* propagated to the resulting #GdkPainable which can be retrieved
* using mks_screen_attach_finish() from @callback.
*/
void
mks_screen_attach (MksScreen *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(MksPaintableListener) listener = NULL;
g_autoptr(GUnixFDList) unix_fd_list = NULL;
g_autoptr(GDBusConnection) connection = NULL;
g_autoptr(GSocketConnection) io_stream = NULL;
g_autoptr(GSocket) socket = NULL;
g_autoptr(GTask) task = NULL;
g_autoptr(GError) error = NULL;
g_autofd int us = -1;
g_autofd int them = -1;
g_return_if_fail (MKS_IS_SCREEN (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, mks_screen_attach);
if (!check_console (self, &error) ||
!mks_screen_create_socketpair (&us, &them, &error))
goto failure;
g_assert (us != -1);
g_assert (them != -1);
if (!(socket = g_socket_new_from_fd (us, &error)))
goto failure;
us = -1;
io_stream = g_socket_connection_factory_create_connection (socket);
connection = g_dbus_connection_new_sync (G_IO_STREAM (io_stream),
NULL,
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
NULL,
cancellable,
&error);
if (connection == NULL)
goto failure;
unix_fd_list = g_unix_fd_list_new_from_array (&them, 1);
them = -1;
listener = mks_paintable_listener_new ();
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (listener),
connection,
MKS_PAINTABLE_LISTENER_OBJECT_PATH,
&error))
goto failure;
g_task_set_task_data (task,
_mks_paintable_new (connection, self, listener),
g_object_unref);
mks_qemu_console_call_register_listener (self->console,
g_variant_new_handle (0),
unix_fd_list,
cancellable,
mks_screen_attach_cb,
g_steal_pointer (&task));
g_dbus_connection_start_message_processing (connection);
return;
failure:
g_task_return_error (task, g_steal_pointer (&error));
}
/**
* mks_screen_attach_finish:
* @self: an #MksScreen
* @result: a #GAsyncResult provided to callback
* @error: a location for a #GError, or %NULL
*
* Completes an asynchronous request to create a #GdkPaintable containing
* the contents of #MksScreen in the Qemu instance.
*
* The resulting #GdkPaintable will be updated as changes are delivered
* from Qemu over a private `socketpair()`. In the typical case, those
* changes are propagated using a DMA-BUF and damage notifications.
*
* Returns: (transfer full): a #GdkPainable if successful; otherwise %NULL
* and @error is set.
*/
GdkPaintable *
mks_screen_attach_finish (MksScreen *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (MKS_IS_SCREEN (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
return g_task_propagate_pointer (G_TASK (result), error);
}
GdkPaintable *
mks_screen_attach_sync (MksScreen *self,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GUnixFDList) unix_fd_list = NULL;
g_autoptr(GDBusConnection) connection = NULL;
g_autoptr(GSocketConnection) io_stream = NULL;
g_autoptr(MksPaintableListener) listener = NULL;
g_autoptr(GSocket) socket = NULL;
g_autofd int us = -1;
g_autofd int them = -1;
g_return_val_if_fail (MKS_IS_SCREEN (self), NULL);
g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
if (!check_console (self, error) ||
!mks_screen_create_socketpair (&us, &them, error))
return NULL;
g_assert (us != -1);
g_assert (them != -1);
if (!(socket = g_socket_new_from_fd (us, error)))
return NULL;
us = -1;
io_stream = g_socket_connection_factory_create_connection (socket);
connection = g_dbus_connection_new_sync (G_IO_STREAM (io_stream),
NULL,
G_DBUS_CONNECTION_FLAGS_NONE,
NULL,
cancellable,
error);
if (connection == NULL)
return FALSE;
unix_fd_list = g_unix_fd_list_new_from_array (&them, 1);
them = -1;
listener = mks_paintable_listener_new ();
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (listener),
connection,
MKS_PAINTABLE_LISTENER_OBJECT_PATH,
error))
return NULL;
if (!mks_qemu_console_call_register_listener_sync (self->console,
g_variant_new_handle (0),
unix_fd_list,
NULL,
cancellable,
error))
return NULL;
return _mks_paintable_new (connection, self, listener);
}

View File

@ -25,6 +25,7 @@
# error "Only <libmks.h> can be included directly." # error "Only <libmks.h> can be included directly."
#endif #endif
#include <gdk/gdk.h>
#include <gio/gio.h> #include <gio/gio.h>
#include "mks-types.h" #include "mks-types.h"
@ -79,6 +80,19 @@ gboolean mks_screen_configure_sync (MksScreen *self,
MksScreenAttributes *attributes, MksScreenAttributes *attributes,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
MKS_AVAILABLE_IN_ALL
void mks_screen_attach (MksScreen *screen,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
MKS_AVAILABLE_IN_ALL
GdkPaintable *mks_screen_attach_finish (MksScreen *self,
GAsyncResult *result,
GError **error);
MKS_AVAILABLE_IN_ALL
GdkPaintable *mks_screen_attach_sync (MksScreen *screen,
GCancellable *cancellable,
GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MksScreen, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (MksScreen, g_object_unref)