mutter/src/tests/screen-cast-client.c

817 lines
22 KiB
C
Raw Normal View History

/*
* Copyright (C) 2021 Red Hat Inc.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*/
#include "config.h"
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
#include <spa/utils/result.h>
#include <stdint.h>
#include <sys/mman.h>
#include "meta-dbus-remote-desktop.h"
#include "meta-dbus-screen-cast.h"
#define assert_cursor_position(stream, x, y) \
{ \
g_assert_cmpint (stream->cursor_x, ==, (x)); \
g_assert_cmpint (stream->cursor_y, ==, (y)); \
}
#define CURSOR_META_SIZE(width, height) \
(sizeof(struct spa_meta_cursor) + \
sizeof(struct spa_meta_bitmap) + width * height * 4)
enum
{
CURSOR_MODE_HIDDEN = 0,
CURSOR_MODE_EMBEDDED = 1,
CURSOR_MODE_METADATA = 2,
};
typedef struct _Stream
{
MetaDBusScreenCastStream *proxy;
uint32_t pipewire_node_id;
struct spa_video_info_raw spa_format;
struct pw_stream *pipewire_stream;
struct spa_hook pipewire_stream_listener;
enum pw_stream_state state;
int buffer_count;
int target_width;
int target_height;
int cursor_x;
int cursor_y;
} Stream;
typedef struct _Session
{
MetaDBusScreenCastSession *screen_cast_session_proxy;
MetaDBusRemoteDesktopSession *remote_desktop_session_proxy;
} Session;
typedef struct _RemoteDesktop
{
MetaDBusRemoteDesktop *proxy;
} RemoteDesktop;
typedef struct _ScreenCast
{
MetaDBusScreenCast *proxy;
} ScreenCast;
typedef struct _PipeWireSource
{
GSource source;
struct pw_loop *pipewire_loop;
} PipeWireSource;
static GSource *_pipewire_source;
static struct pw_context *_pipewire_context;
static struct pw_core *_pipewire_core;
static struct spa_hook _pipewire_core_listener;
static gboolean
pipewire_loop_source_prepare (GSource *source,
int *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
pipewire_loop_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
PipeWireSource *pipewire_source = (PipeWireSource *) source;
int result;
result = pw_loop_iterate (pipewire_source->pipewire_loop, 0);
if (result < 0)
g_error ("pipewire_loop_iterate failed: %s", spa_strerror (result));
return TRUE;
}
static void
pipewire_loop_source_finalize (GSource *source)
{
PipeWireSource *pipewire_source = (PipeWireSource *) source;
pw_loop_leave (pipewire_source->pipewire_loop);
pw_loop_destroy (pipewire_source->pipewire_loop);
}
static GSourceFuncs pipewire_source_funcs =
{
pipewire_loop_source_prepare,
NULL,
pipewire_loop_source_dispatch,
pipewire_loop_source_finalize
};
static GSource *
create_pipewire_source (struct pw_loop *pipewire_loop)
{
GSource *source;
PipeWireSource *pipewire_source;
source = g_source_new (&pipewire_source_funcs,
sizeof (PipeWireSource));
pipewire_source = (PipeWireSource *) source;
pipewire_source->pipewire_loop = pipewire_loop;
g_source_add_unix_fd (source,
pw_loop_get_fd (pipewire_source->pipewire_loop),
G_IO_IN | G_IO_ERR);
pw_loop_enter (pipewire_source->pipewire_loop);
g_source_attach (source, NULL);
return source;
}
static void
on_core_error (void *user_data,
uint32_t id,
int seq,
int res,
const char *message)
{
g_error ("PipeWire core error: id:%u %s", id, message);
}
static const struct pw_core_events core_events = {
PW_VERSION_CORE_EVENTS,
.error = on_core_error,
};
static void
init_pipewire (void)
{
struct pw_loop *pipewire_loop;
pw_init (NULL, NULL);
pipewire_loop = pw_loop_new (NULL);
g_assert_nonnull (pipewire_loop);
_pipewire_source = create_pipewire_source (pipewire_loop);
_pipewire_context = pw_context_new (pipewire_loop,
NULL, 0);
g_assert_nonnull (_pipewire_context);
_pipewire_core = pw_context_connect (_pipewire_context, NULL, 0);
g_assert_nonnull (_pipewire_core);
pw_core_add_listener (_pipewire_core,
&_pipewire_core_listener,
&core_events,
NULL);
}
static void
release_pipewire (void)
{
g_clear_pointer (&_pipewire_core, pw_core_disconnect);
g_clear_pointer (&_pipewire_context, pw_context_destroy);
if (_pipewire_source)
{
g_source_destroy (_pipewire_source);
g_source_unref (_pipewire_source);
_pipewire_source = NULL;
}
}
static void
on_stream_state_changed (void *user_data,
enum pw_stream_state old,
enum pw_stream_state state,
const char *error)
{
Stream *stream = user_data;
g_debug ("New PipeWire stream (%u) state '%s'",
stream->pipewire_node_id,
pw_stream_state_as_string (state));
switch (state)
{
case PW_STREAM_STATE_ERROR:
g_warning ("PipeWire stream error: %s", error);
break;
case PW_STREAM_STATE_PAUSED:
case PW_STREAM_STATE_STREAMING:
case PW_STREAM_STATE_UNCONNECTED:
case PW_STREAM_STATE_CONNECTING:
break;
}
stream->state = state;
}
static void
on_stream_param_changed (void *user_data,
uint32_t id,
const struct spa_pod *format)
{
Stream *stream = user_data;
uint8_t params_buffer[1024];
struct spa_pod_builder pod_builder;
const struct spa_pod *params[3];
if (!format || id != SPA_PARAM_Format)
return;
spa_format_video_raw_parse (format, &stream->spa_format);
pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer));
params[0] = spa_pod_builder_add_object (
&pod_builder,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int (8, 1, 8),
SPA_PARAM_BUFFERS_dataType, SPA_POD_Int ((1 << SPA_DATA_MemPtr) |
(1 << SPA_DATA_MemFd)),
0);
params[1] = spa_pod_builder_add_object (
&pod_builder,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Header),
SPA_PARAM_META_size, SPA_POD_Int (sizeof (struct spa_meta_header)),
0);
params[2] = spa_pod_builder_add_object (
&pod_builder,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Cursor),
SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int (CURSOR_META_SIZE (384, 384),
CURSOR_META_SIZE (1, 1),
CURSOR_META_SIZE (384, 384)),
0);
pw_stream_update_params (stream->pipewire_stream,
params, G_N_ELEMENTS (params));
}
static void
process_buffer_metadata (Stream *stream,
struct spa_buffer *buffer)
{
struct spa_meta_cursor *spa_meta_cursor;
spa_meta_cursor = spa_buffer_find_meta_data (buffer, SPA_META_Cursor,
sizeof *spa_meta_cursor);
if (!spa_meta_cursor)
return;
if (!spa_meta_cursor_is_valid (spa_meta_cursor))
return;
stream->cursor_x = spa_meta_cursor->position.x;
stream->cursor_y = spa_meta_cursor->position.y;
}
static void
sanity_check_memfd (struct spa_buffer *buffer)
{
size_t size;
uint8_t *map;
size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset;
g_assert_cmpint (size, >, 0);
map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, buffer->datas[0].fd, 0);
g_assert (map != MAP_FAILED);
munmap (map, size);
}
static void
sanity_check_memptr (struct spa_buffer *buffer)
{
size_t size;
size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset;
g_assert_cmpint (size, >, 0);
g_assert_nonnull (buffer->datas[0].data);
}
static void
process_buffer (Stream *stream,
struct spa_buffer *buffer)
{
process_buffer_metadata (stream, buffer);
if (buffer->datas[0].chunk->size == 0)
g_assert_not_reached ();
else if (buffer->datas[0].type == SPA_DATA_MemFd)
sanity_check_memfd (buffer);
else if (buffer->datas[0].type == SPA_DATA_DmaBuf)
g_assert_not_reached ();
else if (buffer->datas[0].type == SPA_DATA_MemPtr)
sanity_check_memptr (buffer);
else
g_assert_not_reached ();
}
static void
on_stream_process (void *user_data)
{
Stream *stream = user_data;
struct pw_buffer *next_buffer;
struct pw_buffer *buffer = NULL;
tests/screen-cast-client: Don't process stream on tear down We'd get a re-entry like scenario when destroying the PipeWire stream object, where PipeWire would call the stream process vfunc. When this happened, we had already destroyed the stream, so don't try to dequeue or anything, just do an early exit. Fixes the following crash in the test case client: #0 pw_stream_dequeue_buffer() in /usr/lib64/libpipewire-0.3.so.0.367.0 #1 on_stream_process() at ../src/tests/screen-cast-client.c:348 #2 do_call_process() in /usr/lib64/libpipewire-0.3.so.0.367.0 #3 flush_items() in /usr/lib64/spa-0.2/support/libspa-support.so #4 loop_invoke() in /usr/lib64/spa-0.2/support/libspa-support.so #5 impl_send_command.lto_priv.0() in /usr/lib64/libpipewire-0.3.so.0.367.0 #6 suspend_node.lto_priv.0() in /usr/lib64/libpipewire-0.3.so.0.367.0 #7 pw_impl_node_set_state() in /usr/lib64/libpipewire-0.3.so.0.367.0 #8 client_node_removed() in /usr/lib64/pipewire-0.3/libpipewire-module-client-node.so #9 pw_proxy_destroy() in /usr/lib64/libpipewire-0.3.so.0.367.0 #10 pw_stream_disconnect() in /usr/lib64/libpipewire-0.3.so.0.367.0 #11 pw_stream_destroy() in /usr/lib64/libpipewire-0.3.so.0.367.0 #12 stream_free() at ../src/tests/screen-cast-client.c:530 #13 main() at ../src/tests/screen-cast-client.c:803 #14 __libc_start_call_main() at ../sysdeps/nptl/libc_start_call_main.h:58 #15 __libc_start_main() at ../csu/libc-start.c:360 #16 _start() in /home/jonas/Dev/gnome/mutter/build/src/tests/mutter-screen-cast-client Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3095>
2023-06-26 14:54:04 -04:00
if (!stream->pipewire_stream)
return;
next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream);
if (next_buffer)
g_debug ("Dequeued buffer, queue previous");
while (next_buffer)
{
buffer = next_buffer;
next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream);
if (next_buffer)
{
g_debug ("Dequeued another buffer, queuing previous");
pw_stream_queue_buffer (stream->pipewire_stream, buffer);
}
}
if (!buffer)
return;
process_buffer (stream, buffer->buffer);
pw_stream_queue_buffer (stream->pipewire_stream, buffer);
stream->buffer_count++;
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.state_changed = on_stream_state_changed,
.param_changed = on_stream_param_changed,
.process = on_stream_process,
};
static void
stream_connect (Stream *stream)
{
struct pw_stream *pipewire_stream;
uint8_t params_buffer[1024];
struct spa_pod_builder pod_builder;
struct spa_rectangle rect;
struct spa_fraction min_framerate;
struct spa_fraction max_framerate;
const struct spa_pod *params[2];
int ret;
pipewire_stream = pw_stream_new (_pipewire_core,
"mutter-test-pipewire-stream",
NULL);
rect = SPA_RECTANGLE (stream->target_width, stream->target_height);
min_framerate = SPA_FRACTION (1, 1);
max_framerate = SPA_FRACTION (30, 1);
pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer));
params[0] = spa_pod_builder_add_object (
&pod_builder,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_video),
SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_VIDEO_format, SPA_POD_Id (SPA_VIDEO_FORMAT_BGRx),
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle (&rect),
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&SPA_FRACTION(0, 1)),
SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction (&min_framerate,
&min_framerate,
&max_framerate),
0);
stream->pipewire_stream = pipewire_stream;
pw_stream_add_listener (pipewire_stream,
&stream->pipewire_stream_listener,
&stream_events,
stream);
ret = pw_stream_connect (stream->pipewire_stream,
PW_DIRECTION_INPUT,
stream->pipewire_node_id,
PW_STREAM_FLAG_AUTOCONNECT,
params, 1);
if (ret < 0)
g_error ("Failed to connect PipeWire stream: %s", g_strerror (-ret));
}
static void
stream_wait_for_node (Stream *stream)
{
while (!stream->pipewire_node_id)
g_main_context_iteration (NULL, TRUE);
}
static void
stream_wait_for_cursor_position (Stream *stream,
int x,
int y)
{
while (stream->cursor_x != x &&
stream->cursor_y != y)
g_main_context_iteration (NULL, TRUE);
}
static G_GNUC_UNUSED void
stream_wait_for_streaming (Stream *stream)
{
while (stream->state != PW_STREAM_STATE_STREAMING)
g_main_context_iteration (NULL, TRUE);
}
static G_GNUC_UNUSED void
stream_wait_for_render (Stream *stream)
{
int initial_buffer_count = stream->buffer_count;
while (stream->buffer_count == initial_buffer_count)
g_main_context_iteration (NULL, TRUE);
}
static void
stream_resize (Stream *stream,
int width,
int height)
{
uint8_t params_buffer[1024];
struct spa_pod_builder pod_builder;
const struct spa_pod *params[1];
struct spa_rectangle rect;
stream->target_width = width;
stream->target_height = height;
rect = SPA_RECTANGLE (width, height);
pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer));
params[0] = spa_pod_builder_add_object (
&pod_builder,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle (&rect),
0);
pw_stream_update_params (stream->pipewire_stream,
params, G_N_ELEMENTS (params));
}
static void
on_pipewire_stream_added (MetaDBusScreenCastStream *proxy,
unsigned int node_id,
Stream *stream)
{
stream->pipewire_node_id = (uint32_t) node_id;
stream_connect (stream);
}
static Stream *
stream_new (const char *path,
int width,
int height)
{
Stream *stream;
GError *error = NULL;
stream = g_new0 (Stream, 1);
stream->target_width = width;
stream->target_height = height;
stream->proxy = meta_dbus_screen_cast_stream_proxy_new_for_bus_sync (
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
"org.gnome.Mutter.ScreenCast",
path,
NULL,
&error);
if (!stream->proxy)
g_error ("Failed to acquire proxy: %s", error->message);
g_signal_connect (stream->proxy, "pipewire-stream-added",
G_CALLBACK (on_pipewire_stream_added),
stream);
return stream;
}
static void
stream_free (Stream *stream)
{
g_clear_pointer (&stream->pipewire_stream, pw_stream_destroy);
g_clear_object (&stream->proxy);
g_free (stream);
}
static void
session_notify_absolute_pointer (Session *session,
Stream *stream,
double x,
double y)
{
GError *error = NULL;
if (!meta_dbus_remote_desktop_session_call_notify_pointer_motion_absolute_sync (
session->remote_desktop_session_proxy,
g_dbus_proxy_get_object_path (G_DBUS_PROXY (stream->proxy)),
x, y, NULL, &error))
g_error ("Failed to send absolute pointer motion event: %s", error->message);
}
static void
session_start (Session *session)
{
GError *error = NULL;
if (!meta_dbus_remote_desktop_session_call_start_sync (
session->remote_desktop_session_proxy,
NULL,
&error))
g_error ("Failed to start session: %s", error->message);
}
static void
session_stop (Session *session)
{
GError *error = NULL;
if (!meta_dbus_remote_desktop_session_call_stop_sync (
session->remote_desktop_session_proxy,
NULL,
&error))
g_error ("Failed to stop session: %s", error->message);
}
static Stream *
session_record_virtual (Session *session,
int width,
int height)
{
GVariantBuilder properties_builder;
GVariant *properties_variant;
GError *error = NULL;
g_autofree char *stream_path = NULL;
Stream *stream;
g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&properties_builder, "{sv}",
"cursor-mode",
g_variant_new_uint32 (CURSOR_MODE_METADATA));
properties_variant = g_variant_builder_end (&properties_builder);
if (!meta_dbus_screen_cast_session_call_record_virtual_sync (
session->screen_cast_session_proxy,
properties_variant,
&stream_path,
NULL,
&error))
g_error ("Failed to create session: %s", error->message);
stream = stream_new (stream_path, width, height);
g_assert_nonnull (stream);
return stream;
}
static Session *
session_new (MetaDBusRemoteDesktopSession *remote_desktop_session_proxy,
MetaDBusScreenCastSession *screen_cast_session_proxy)
{
Session *session;
session = g_new0 (Session, 1);
session->remote_desktop_session_proxy = remote_desktop_session_proxy;
session->screen_cast_session_proxy = screen_cast_session_proxy;
return session;
}
static void
session_free (Session *session)
{
g_clear_object (&session->screen_cast_session_proxy);
g_clear_object (&session->remote_desktop_session_proxy);
g_free (session);
}
static Session *
screen_cast_create_session (RemoteDesktop *remote_desktop,
ScreenCast *screen_cast)
{
GVariantBuilder properties_builder;
GError *error = NULL;
g_autofree char *remote_desktop_session_path = NULL;
MetaDBusRemoteDesktopSession *remote_desktop_session_proxy;
g_autofree char *screen_cast_session_path = NULL;
MetaDBusScreenCastSession *screen_cast_session_proxy;
const char *session_id;
Session *session;
if (!meta_dbus_remote_desktop_call_create_session_sync (
remote_desktop->proxy,
&remote_desktop_session_path,
NULL,
&error))
g_error ("Failed to create session: %s", error->message);
remote_desktop_session_proxy =
meta_dbus_remote_desktop_session_proxy_new_for_bus_sync (
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
"org.gnome.Mutter.RemoteDesktop",
remote_desktop_session_path,
NULL,
&error);
if (!remote_desktop_session_proxy)
g_error ("Failed to acquire proxy: %s", error->message);
session_id =
meta_dbus_remote_desktop_session_get_session_id (
remote_desktop_session_proxy);
g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&properties_builder, "{sv}",
"remote-desktop-session-id",
g_variant_new_string (session_id));
if (!meta_dbus_screen_cast_call_create_session_sync (
screen_cast->proxy,
g_variant_builder_end (&properties_builder),
&screen_cast_session_path,
NULL,
&error))
g_error ("Failed to create session: %s", error->message);
screen_cast_session_proxy =
meta_dbus_screen_cast_session_proxy_new_for_bus_sync (
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
"org.gnome.Mutter.ScreenCast",
screen_cast_session_path,
NULL,
&error);
if (!screen_cast_session_proxy)
g_error ("Failed to acquire proxy: %s", error->message);
session = session_new (remote_desktop_session_proxy,
screen_cast_session_proxy);
g_assert_nonnull (session);
return session;
}
static RemoteDesktop *
remote_desktop_new (void)
{
RemoteDesktop *remote_desktop;
GError *error = NULL;
remote_desktop = g_new0 (RemoteDesktop, 1);
remote_desktop->proxy = meta_dbus_remote_desktop_proxy_new_for_bus_sync (
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
"org.gnome.Mutter.RemoteDesktop",
"/org/gnome/Mutter/RemoteDesktop",
NULL,
&error);
if (!remote_desktop->proxy)
g_error ("Failed to acquire proxy: %s", error->message);
return remote_desktop;
}
static void
remote_desktop_free (RemoteDesktop *remote_desktop)
{
g_clear_object (&remote_desktop->proxy);
g_free (remote_desktop);
}
static ScreenCast *
screen_cast_new (void)
{
ScreenCast *screen_cast;
GError *error = NULL;
screen_cast = g_new0 (ScreenCast, 1);
screen_cast->proxy = meta_dbus_screen_cast_proxy_new_for_bus_sync (
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
"org.gnome.Mutter.ScreenCast",
"/org/gnome/Mutter/ScreenCast",
NULL,
&error);
if (!screen_cast->proxy)
g_error ("Failed to acquire proxy: %s", error->message);
return screen_cast;
}
static void
screen_cast_free (ScreenCast *screen_cast)
{
g_clear_object (&screen_cast->proxy);
g_free (screen_cast);
}
int
main (int argc,
char **argv)
{
RemoteDesktop *remote_desktop;
ScreenCast *screen_cast;
Session *session;
Stream *stream;
g_debug ("Initializing PipeWire");
init_pipewire ();
g_debug ("Creating screen cast session");
remote_desktop = remote_desktop_new ();
screen_cast = screen_cast_new ();
session = screen_cast_create_session (remote_desktop, screen_cast);
stream = session_record_virtual (session, 50, 40);
g_debug ("Starting screen cast stream");
session_start (session);
/* Check that the display server handles events being emitted too early. */
session_notify_absolute_pointer (session, stream, 2, 3);
/* Check that we receive the initial frame */
g_debug ("Waiting for stream to be established");
stream_wait_for_node (stream);
stream_wait_for_render (stream);
stream_wait_for_streaming (stream);
session_notify_absolute_pointer (session, stream, 6, 5);
session_notify_absolute_pointer (session, stream, 5, 6);
g_debug ("Waiting for frame");
stream_wait_for_render (stream);
stream_wait_for_cursor_position (stream, 5, 6);
g_assert_cmpint (stream->spa_format.size.width, ==, 50);
g_assert_cmpint (stream->spa_format.size.height, ==, 40);
/* Check that resizing works */
g_debug ("Resizing stream");
stream_resize (stream, 70, 60);
while (TRUE)
{
stream_wait_for_render (stream);
if (stream->spa_format.size.width == 70 &&
stream->spa_format.size.height == 60)
break;
g_assert_cmpint (stream->spa_format.size.width, ==, 50);
g_assert_cmpint (stream->spa_format.size.height, ==, 40);
}
/* Check that resizing works */
stream_resize (stream, 60, 60);
g_debug ("Stopping session");
session_stop (session);
stream_free (stream);
session_free (session);
screen_cast_free (screen_cast);
remote_desktop_free (remote_desktop);
release_pipewire ();
g_debug ("Done");
return EXIT_SUCCESS;
}