diff --git a/lib/mks-paintable-private.h b/lib/mks-paintable-private.h new file mode 100644 index 0000000..bf3aae2 --- /dev/null +++ b/lib/mks-paintable-private.h @@ -0,0 +1,33 @@ +/* + * mks-paintable-private.h + * + * Copyright 2023 Christian Hergert + * + * 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 . + * + * 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 diff --git a/lib/mks-paintable.c b/lib/mks-paintable.c index b045886..f8ea875 100644 --- a/lib/mks-paintable.c +++ b/lib/mks-paintable.c @@ -21,7 +21,7 @@ #include "config.h" -#include "mks-paintable.h" +#include "mks-paintable-private.h" #include "mks-screen.h" struct _MksPaintable @@ -224,3 +224,17 @@ paintable_iface_init (GdkPaintableInterface *iface) iface->get_intrinsic_aspect_ratio = mks_paintable_get_intrinsic_aspect_ratio; 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); +} diff --git a/lib/mks-screen.c b/lib/mks-screen.c index 7182a1e..27c6cbb 100644 --- a/lib/mks-screen.c +++ b/lib/mks-screen.c @@ -21,11 +21,18 @@ #include "config.h" +#include +#include + +#include + #include "mks-device-private.h" #include "mks-enums.h" #include "mks-qemu.h" #include "mks-keyboard-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-private.h" @@ -556,3 +563,226 @@ mks_screen_configure_sync (MksScreen *self, cancellable, 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); +} diff --git a/lib/mks-screen.h b/lib/mks-screen.h index e2385dc..7e2ce95 100644 --- a/lib/mks-screen.h +++ b/lib/mks-screen.h @@ -25,6 +25,7 @@ # error "Only can be included directly." #endif +#include #include #include "mks-types.h" @@ -79,6 +80,19 @@ gboolean mks_screen_configure_sync (MksScreen *self, MksScreenAttributes *attributes, GCancellable *cancellable, 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)