From 1b8efba70899cc21923783743dce841a2bf0a4dc Mon Sep 17 00:00:00 2001 From: Juan Pablo Ugarte Date: Wed, 25 Jun 2025 00:04:04 -0400 Subject: [PATCH] CasildaCompositor: add socketpair support - Add casilda_compositor_get_client_socket_fd() - Add casilda_compositor_spawn_async() Closes issue #8 "Investigate using WAYLAND_SOCKET" --- examples/compositor.c | 34 +++++--- src/casilda-compositor.c | 184 +++++++++++++++++++++++++++++++-------- src/casilda-compositor.h | 12 +++ 3 files changed, 183 insertions(+), 47 deletions(-) diff --git a/examples/compositor.c b/examples/compositor.c index 4877ce7..3f55fe8 100644 --- a/examples/compositor.c +++ b/examples/compositor.c @@ -1,33 +1,47 @@ #include #include "casilda-compositor.h" +/* Do not listen on any socket by default */ +gchar *socket = NULL; + + static void -activate (GtkApplication *app, - G_GNUC_UNUSED gpointer user_data) +activate (GtkApplication *app, G_GNUC_UNUSED gpointer user_data) { CasildaCompositor *compositor; GtkWidget *window; - g_autofree char *socket = NULL; window = gtk_application_window_new (app); - gtk_window_set_title (GTK_WINDOW (window), "Compositor"); + gtk_window_set_title (GTK_WINDOW (window), "Casilda Compositor"); gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); - socket = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), - "casilda-example.sock", NULL); + if (socket) + g_message ("Listening on %s", socket); + else + g_message ("Not listening in any socket, you can pass a socket path as parameter"); + compositor = casilda_compositor_new (socket); gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (compositor)); gtk_window_present (GTK_WINDOW (window)); + + gchar *argv[2] = { + "/usr/bin/gtk4-demo", + NULL + }; + + casilda_compositor_spawn_async (compositor, NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL); } int -main (int argc, - char **argv) +main (int argc, char **argv) { g_autoptr(GtkApplication) app = NULL; - app = gtk_application_new ("org.gnome.casilda.compositor", G_APPLICATION_DEFAULT_FLAGS); + app = gtk_application_new ("ar.xjuan.casilda.Example", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); - return g_application_run (G_APPLICATION (app), argc, argv); + if (argc >= 2) + socket = argv[1]; + + return g_application_run (G_APPLICATION (app), 0, NULL); } diff --git a/src/casilda-compositor.c b/src/casilda-compositor.c index d28d049..b0b257c 100644 --- a/src/casilda-compositor.c +++ b/src/casilda-compositor.c @@ -29,6 +29,8 @@ #define ENABLE_GL_DEBUG 0 #define ENABLE_BORDERS 0 +#include +#include #include #include #include @@ -162,9 +164,10 @@ typedef struct GdkTexture *cursor_gdk_texture; GdkCursor *cursor_gdk_cursor; + struct sockaddr_un abstract_socket; + /* GObject properties */ gchar *socket; - gboolean owns_socket; } CasildaCompositorPrivate; @@ -1675,14 +1678,6 @@ casilda_compositor_finalize (GObject *object) CasildaCompositorPrivate *priv = GET_PRIVATE (object); g_clear_pointer (&priv->toplevel_state, g_hash_table_destroy); - - if (priv->owns_socket) - { - priv->owns_socket = FALSE; - g_autofree char *socket_path = g_path_get_basename (priv->socket); - g_unlink (priv->socket); - g_rmdir (socket_path); - } g_clear_pointer (&priv->socket, g_free); wl_display_destroy_clients (priv->wl_display); @@ -1713,7 +1708,6 @@ casilda_compositor_set_property (GObject *object, { case PROP_SOCKET: g_set_str (&priv->socket, g_value_get_string (value)); - priv->owns_socket = priv->socket != NULL; break; case PROP_BG_COLOR: @@ -1807,9 +1801,12 @@ casilda_compositor_class_init (CasildaCompositorClass *klass) * * Wayland socket the compositor will listen on. * + * If NULL you can still use WAYLAND_SOCKET with an fd returned by + * casilda_compositor_get_client_socket_fd() instead of setting up WAYLAND_DISPLAY. + * */ properties[PROP_SOCKET] = - g_param_spec_string ("socket", "", "", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_param_spec_string ("socket", "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * CasildaCompositor:bg-color: @@ -1824,13 +1821,6 @@ casilda_compositor_class_init (CasildaCompositorClass *klass) g_object_class_install_properties (object_class, N_PROPERTIES, properties); } - -CasildaCompositor * -casilda_compositor_new (const gchar *socket) -{ - return g_object_new (CASILDA_COMPOSITOR_TYPE, "socket", socket, NULL); -} - /* wlroots */ static void @@ -2235,15 +2225,6 @@ server_request_activate (struct wl_listener *listener, void *data) } } -static gchar * -casilda_compositor_get_socket (void) -{ - g_autofree char *tmp = g_dir_make_tmp ("casilda-compositor-XXXXXX", NULL); - g_autofree char *retval = g_build_filename (tmp, "wayland.sock", NULL); - - return g_steal_pointer (&retval); -} - #if ENABLE_GL_DEBUG static void GLAPIENTRY on_gl_message_callback (G_GNUC_UNUSED GLenum source, @@ -2388,21 +2369,150 @@ casilda_compositor_wlr_init (CasildaCompositorPrivate *priv) /* Configure seat */ priv->seat = wlr_seat_create (priv->wl_display, "seat0"); priv->request_set_selection.notify = seat_request_set_selection; - wl_signal_add (&priv->seat->events.request_set_selection, - &priv->request_set_selection); + wl_signal_add (&priv->seat->events.request_set_selection, &priv->request_set_selection); - wlr_seat_set_capabilities (priv->seat, - WL_SEAT_CAPABILITY_POINTER | - WL_SEAT_CAPABILITY_KEYBOARD); + wlr_seat_set_capabilities (priv->seat, WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD); if (!priv->socket) - { - priv->socket = casilda_compositor_get_socket (); - priv->owns_socket = TRUE; - } + return; - if (wl_display_add_socket (priv->wl_display, priv->socket) != 0) + if (wl_display_add_socket (priv->wl_display, priv->socket)) g_warning ("Error adding socket file %s", priv->socket); + else + g_info ("Listening on %s", priv->socket); } +/* Public API */ + +CasildaCompositor * +casilda_compositor_new (const gchar *socket) +{ + return g_object_new (CASILDA_COMPOSITOR_TYPE, "socket", socket, NULL); +} + + +/** + * casilda_compositor_get_client_socket_fd: + * @compositor: a `CasildaCompositor` + * + * Create a client socket file descriptor connected to this compositor ready to use by passing it to a client with + * WAYLAND_SOCKET environment variable. + * + * Returns: a socket FD connected to the compositor + */ +int +casilda_compositor_get_client_socket_fd (CasildaCompositor *compositor) +{ + g_return_val_if_fail (CASILDA_IS_COMPOSITOR (compositor), -1); + + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + gint fds[2] = {0, }; + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds)) + { + g_warning ("Could not create a UNIX socketpair"); + return -1; + } + + if (!wl_client_create (priv->wl_display, fds[0])) + { + g_warning ("Could not add socketpair to display"); + return -1; + } + + return fds[1]; +} + + +/** + * casilda_compositor_spawn_async: + * @compositor: a `CasildaCompositor` + * @working_directory: (type filename) (nullable): child's current working directory, or %NULL to inherit parent's + * @argv: (array zero-terminated=1) (element-type filename): child's argument vector + * @envp: (array zero-terminated=1) (element-type filename) (nullable): child's environment, or %NULL to inherit parent's + * @flags: flags from #GSpawnFlags + * @child_setup: (scope async) (closure user_data) (nullable): function to run in the child just before `exec()` + * @user_data: user data for @child_setup + * @child_pid: (out) (optional): return location for child process reference, or %NULL + * @error: return location for error + * + * Executes a child program asynchronously with the right environment to automatically connect to this compositor. + * + * See g_spawn_async_with_pipes_and_fds() for a full description; this function simply calls + * g_spawn_async_with_pipes_and_fds() without any pipes and with WAYLAND_DISPLAY set to `CasildaCompositor::socket` or + * a fd already connected to the compositor and set to WAYLAND_SOCKET if its NULL. + * + * Please note GDK_BACKEND, WAYLAND_DISPLAY and WAYLAND_SOCKET are set by this function they are ignored from %envp and + * DISPLAY is removed to avoid clients try to connect using X11 + * + * Returns: TRUE on success, FALSE if error is set. + */ +gboolean +casilda_compositor_spawn_async (CasildaCompositor *compositor, + const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + GError **error) +{ + g_return_val_if_fail (CASILDA_IS_COMPOSITOR (compositor), FALSE); + + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + g_autoptr(GStrvBuilder) builder = g_strv_builder_new (); + g_autofree gchar *wayland_socket = NULL; + g_auto(GStrv) environ = NULL; + GStrv source_env = NULL; + gint fd; + + if (envp) + source_env = envp; + else + source_env = environ = g_get_environ(); + + /* Build environment */ + for (gint i = 0; source_env[i]; i++) + { + if (g_str_has_prefix (source_env[i], "DISPLAY=")) + continue; + if (g_str_has_prefix (source_env[i], "GDK_BACKEND=")) + continue; + if (g_str_has_prefix (source_env[i], "WAYLAND_DISPLAY=")) + continue; + if (g_str_has_prefix (source_env[i], "WAYLAND_SOCKET=")) + continue; + + g_strv_builder_add (builder, source_env[i]); + } + + /* Force GTK apps to wayland backend */ + g_strv_builder_add (builder, "GDK_BACKEND=wayland"); + + if (priv->socket) + wayland_socket = g_strdup_printf("WAYLAND_DISPLAY=%s", priv->socket); + else + { + fd = casilda_compositor_get_client_socket_fd (compositor); + wayland_socket = g_strdup_printf("WAYLAND_SOCKET=%d", fd); + } + + /* Add wayland socket env var */ + g_strv_builder_add (builder, wayland_socket); + + g_auto(GStrv) env = g_strv_builder_end (builder); + + return g_spawn_async_with_pipes_and_fds (working_directory, + (const gchar* const* ) argv, + (const gchar* const* ) env, + flags, + child_setup, + user_data, + -1, -1, -1, + &fd, &fd, priv->socket ? 0 : 1, + child_pid, + NULL, NULL, NULL, + error); +} diff --git a/src/casilda-compositor.h b/src/casilda-compositor.h index 4750e92..2a76037 100644 --- a/src/casilda-compositor.h +++ b/src/casilda-compositor.h @@ -31,3 +31,15 @@ G_DECLARE_FINAL_TYPE (CasildaCompositor, casilda_compositor, CASILDA, COMPOSITOR, GtkWidget) CasildaCompositor *casilda_compositor_new (const gchar *socket); + +int casilda_compositor_get_client_socket_fd (CasildaCompositor *compositor); + +gboolean casilda_compositor_spawn_async (CasildaCompositor *compositor, + const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + GError **error);