Files
grd/grd-rdp-layout-manager.c
2026-02-13 13:06:50 +09:00

1044 lines
34 KiB
C

/*
* Copyright (C) 2023 Pascal Nowack
*
* 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 "grd-rdp-layout-manager.h"
#include "grd-context.h"
#include "grd-rdp-cursor-renderer.h"
#include "grd-rdp-pipewire-stream.h"
#include "grd-rdp-renderer.h"
#include "grd-rdp-server.h"
#include "grd-rdp-session-metrics.h"
#include "grd-rdp-surface.h"
#include "grd-session-rdp.h"
#include "grd-settings-user.h"
#include "grd-stream.h"
#define LAYOUT_RECREATION_TIMEOUT_MS 50
#define TARGET_SURFACE_REFRESH_RATE 60
typedef enum
{
UPDATE_STATE_FATAL_ERROR,
UPDATE_STATE_AWAIT_CONFIG,
UPDATE_STATE_AWAIT_INHIBITION_DONE,
UPDATE_STATE_PREPARE_SURFACES,
UPDATE_STATE_AWAIT_STREAMS,
UPDATE_STATE_AWAIT_VIDEO_SIZES,
UPDATE_STATE_START_RENDERING,
} UpdateState;
typedef struct
{
GrdRdpLayoutManager *layout_manager;
GrdRdpSurface *rdp_surface;
uint32_t output_origin_x;
uint32_t output_origin_y;
GrdRdpVirtualMonitor *virtual_monitor;
const char *connector;
uint32_t stream_id;
GrdStream *stream;
GrdRdpPipeWireStream *pipewire_stream;
} SurfaceContext;
struct _GrdRdpLayoutManager
{
GrdRdpStreamOwner parent;
gboolean session_started;
GMutex state_mutex;
UpdateState state;
GrdSessionRdp *session_rdp;
GSource *layout_update_source;
GSource *preparation_source;
GHashTable *surface_table;
GHashTable *pending_streams;
GHashTable *pending_video_sizes;
GMutex monitor_config_mutex;
GrdRdpMonitorConfig *current_monitor_config;
GrdRdpMonitorConfig *pending_monitor_config;
unsigned long inhibition_done_id;
unsigned int layout_recreation_id;
};
G_DEFINE_TYPE (GrdRdpLayoutManager, grd_rdp_layout_manager,
GRD_TYPE_RDP_STREAM_OWNER)
static SurfaceContext *
surface_context_new (GrdRdpLayoutManager *layout_manager,
GError **error)
{
g_autofree SurfaceContext *surface_context = NULL;
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
surface_context = g_new0 (SurfaceContext, 1);
surface_context->layout_manager = layout_manager;
surface_context->rdp_surface =
grd_rdp_renderer_try_acquire_surface (renderer,
TARGET_SURFACE_REFRESH_RATE);
if (!surface_context->rdp_surface)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create RDP surface");
return NULL;
}
return g_steal_pointer (&surface_context);
}
static void
surface_context_free (SurfaceContext *surface_context)
{
GrdRdpLayoutManager *layout_manager = surface_context->layout_manager;
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
g_clear_object (&surface_context->pipewire_stream);
if (surface_context->stream)
{
grd_stream_disconnect_proxy_signals (surface_context->stream);
g_clear_pointer (&surface_context->stream, grd_stream_destroy);
}
grd_session_rdp_release_stream_id (layout_manager->session_rdp,
surface_context->stream_id);
grd_rdp_renderer_release_surface (renderer, surface_context->rdp_surface);
g_clear_pointer (&surface_context->virtual_monitor, g_free);
g_free (surface_context);
}
static const char *
update_state_to_string (UpdateState state)
{
switch (state)
{
case UPDATE_STATE_FATAL_ERROR:
return "FATAL_ERROR";
case UPDATE_STATE_AWAIT_CONFIG:
return "AWAIT_CONFIG";
case UPDATE_STATE_AWAIT_INHIBITION_DONE:
return "AWAIT_INHIBITION_DONE";
case UPDATE_STATE_PREPARE_SURFACES:
return "PREPARE_SURFACES";
case UPDATE_STATE_AWAIT_STREAMS:
return "AWAIT_STREAMS";
case UPDATE_STATE_AWAIT_VIDEO_SIZES:
return "AWAIT_VIDEO_SIZES";
case UPDATE_STATE_START_RENDERING:
return "START_RENDERING";
}
g_assert_not_reached ();
}
static void
disconnect_proxy_signals (GrdRdpLayoutManager *layout_manager)
{
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
if (!surface_context->stream)
continue;
grd_stream_disconnect_proxy_signals (surface_context->stream);
}
}
static void
transition_to_state (GrdRdpLayoutManager *layout_manager,
UpdateState state)
{
g_mutex_lock (&layout_manager->state_mutex);
g_debug ("[RDP] Layout manager: Updating state from %s to %s",
update_state_to_string (layout_manager->state),
update_state_to_string (state));
g_assert (layout_manager->state != state);
layout_manager->state = state;
g_mutex_unlock (&layout_manager->state_mutex);
switch (state)
{
case UPDATE_STATE_FATAL_ERROR:
disconnect_proxy_signals (layout_manager);
grd_session_rdp_notify_error (layout_manager->session_rdp,
GRD_SESSION_RDP_ERROR_CLOSE_STACK_ON_DRIVER_FAILURE);
break;
case UPDATE_STATE_AWAIT_CONFIG:
g_debug ("[RDP] Layout manager: Finished applying new monitor config");
g_source_set_ready_time (layout_manager->layout_update_source, 0);
break;
case UPDATE_STATE_AWAIT_INHIBITION_DONE:
break;
case UPDATE_STATE_PREPARE_SURFACES:
g_source_set_ready_time (layout_manager->layout_update_source, 0);
break;
case UPDATE_STATE_AWAIT_STREAMS:
case UPDATE_STATE_AWAIT_VIDEO_SIZES:
break;
case UPDATE_STATE_START_RENDERING:
g_source_set_ready_time (layout_manager->layout_update_source, 0);
break;
}
}
static void
on_pipewire_stream_error (GrdRdpPipeWireStream *pipewire_stream,
GrdRdpLayoutManager *layout_manager)
{
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR)
return;
g_warning ("[RDP] PipeWire stream closed due to error. Terminating session");
transition_to_state (layout_manager, UPDATE_STATE_FATAL_ERROR);
}
static gboolean
is_virtual_stream (GrdRdpLayoutManager *layout_manager,
GrdRdpPipeWireStream *pipewire_stream)
{
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
if (surface_context->pipewire_stream == pipewire_stream)
return surface_context->virtual_monitor != NULL;
}
g_assert_not_reached ();
}
static gboolean
maybe_invoke_layout_recreation (gpointer user_data)
{
GrdRdpLayoutManager *layout_manager = user_data;
g_mutex_lock (&layout_manager->monitor_config_mutex);
if (!layout_manager->pending_monitor_config)
{
g_debug ("[RDP] Layout manager: Trying to restore last config.");
layout_manager->pending_monitor_config =
g_steal_pointer (&layout_manager->current_monitor_config);
g_source_set_ready_time (layout_manager->layout_update_source, 0);
}
g_mutex_unlock (&layout_manager->monitor_config_mutex);
layout_manager->layout_recreation_id = 0;
return G_SOURCE_REMOVE;
}
static void
on_pipewire_stream_closed (GrdRdpPipeWireStream *pipewire_stream,
GrdRdpLayoutManager *layout_manager)
{
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR ||
is_virtual_stream (layout_manager, pipewire_stream) ||
layout_manager->layout_recreation_id)
return;
g_debug ("[RDP] Layout manager: Monitor stream closed. "
"Setting timeout for recreation");
layout_manager->layout_recreation_id =
g_timeout_add (LAYOUT_RECREATION_TIMEOUT_MS, maybe_invoke_layout_recreation,
layout_manager);
}
static SurfaceContext *
get_surface_context_from_pipewire_stream (GrdRdpLayoutManager *layout_manager,
GrdRdpPipeWireStream *pipewire_stream)
{
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
if (surface_context->pipewire_stream == pipewire_stream)
return surface_context;
}
g_assert_not_reached ();
}
static void
write_virtual_monitor_data (GrdRdpVirtualMonitor *virtual_monitor,
rdpMonitor *monitor)
{
monitor->x = virtual_monitor->pos_x;
monitor->y = virtual_monitor->pos_y;
monitor->width = virtual_monitor->width;
monitor->height = virtual_monitor->height;
monitor->is_primary = virtual_monitor->is_primary;
}
static void
write_connector_data (GrdRdpSurface *rdp_surface,
rdpMonitor *monitor)
{
int32_t surface_width = grd_rdp_surface_get_width (rdp_surface);
int32_t surface_height = grd_rdp_surface_get_height (rdp_surface);
monitor->x = 0;
monitor->y = 0;
monitor->width = surface_width;
monitor->height = surface_height;
monitor->is_primary = TRUE;
}
static void
update_monitor_data (GrdRdpLayoutManager *layout_manager)
{
rdpContext *rdp_context =
grd_session_rdp_get_rdp_context (layout_manager->session_rdp);
rdpSettings *rdp_settings = rdp_context->settings;
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
uint32_t i = 0;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
rdpMonitor monitor = {};
if (surface_context->connector)
g_assert (i == 0);
if (surface_context->virtual_monitor)
write_virtual_monitor_data (surface_context->virtual_monitor, &monitor);
if (surface_context->connector)
write_connector_data (surface_context->rdp_surface, &monitor);
if (!freerdp_settings_set_pointer_array (rdp_settings,
FreeRDP_MonitorDefArray, i++,
&monitor))
g_assert_not_reached ();
}
freerdp_settings_set_uint32 (rdp_settings, FreeRDP_MonitorCount, i);
}
static void
on_pipewire_stream_video_resized (GrdRdpPipeWireStream *pipewire_stream,
uint32_t width,
uint32_t height,
GrdRdpLayoutManager *layout_manager)
{
SurfaceContext *surface_context;
GrdRdpVirtualMonitor *virtual_monitor;
uint32_t stream_id;
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR)
return;
surface_context = get_surface_context_from_pipewire_stream (layout_manager,
pipewire_stream);
virtual_monitor = surface_context->virtual_monitor;
if (virtual_monitor &&
(virtual_monitor->width != width || virtual_monitor->height != height))
{
g_warning ("[RDP] Layout manager: Unexpected video size change of PipeWire "
"stream (Expected: %ux%u, Got: %ux%u). Ignoring resize event...",
virtual_monitor->width, virtual_monitor->height, width, height);
return;
}
if (surface_context->connector)
{
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
g_assert (!layout_manager->current_monitor_config->is_virtual);
g_assert (layout_manager->current_monitor_config->monitor_count == 1);
g_assert (g_hash_table_size (layout_manager->surface_table) == 1);
update_monitor_data (layout_manager);
grd_rdp_renderer_notify_new_desktop_layout (renderer, width, height);
}
stream_id = grd_stream_get_stream_id (surface_context->stream);
if (!g_hash_table_remove (layout_manager->pending_video_sizes,
GUINT_TO_POINTER (stream_id)))
return;
g_assert (layout_manager->state == UPDATE_STATE_AWAIT_STREAMS ||
layout_manager->state == UPDATE_STATE_AWAIT_VIDEO_SIZES);
g_assert (g_hash_table_size (layout_manager->pending_streams) <=
g_hash_table_size (layout_manager->pending_video_sizes));
if (g_hash_table_size (layout_manager->pending_video_sizes) == 0)
transition_to_state (layout_manager, UPDATE_STATE_START_RENDERING);
}
static void
on_stream_ready (GrdStream *stream,
GrdRdpLayoutManager *layout_manager)
{
uint32_t stream_id = grd_stream_get_stream_id (stream);
uint32_t pipewire_node_id = grd_stream_get_pipewire_node_id (stream);
SurfaceContext *surface_context = NULL;
g_autoptr (GError) error = NULL;
g_assert (layout_manager->state == UPDATE_STATE_FATAL_ERROR ||
layout_manager->state == UPDATE_STATE_AWAIT_STREAMS);
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR)
return;
if (!g_hash_table_lookup_extended (layout_manager->surface_table,
GUINT_TO_POINTER (stream_id),
NULL, (gpointer *) &surface_context))
g_assert_not_reached ();
g_assert (stream_id == surface_context->stream_id);
g_assert (!surface_context->pipewire_stream);
g_debug ("[RDP] Layout manager: Creating PipeWire stream for node id %u",
pipewire_node_id);
surface_context->pipewire_stream =
grd_rdp_pipewire_stream_new (layout_manager->session_rdp,
surface_context->rdp_surface,
surface_context->virtual_monitor,
pipewire_node_id,
&error);
if (!surface_context->pipewire_stream)
{
g_warning ("[RDP] Failed to establish PipeWire stream: %s",
error->message);
transition_to_state (layout_manager, UPDATE_STATE_FATAL_ERROR);
return;
}
g_signal_connect (surface_context->pipewire_stream, "error",
G_CALLBACK (on_pipewire_stream_error),
layout_manager);
g_signal_connect (surface_context->pipewire_stream, "closed",
G_CALLBACK (on_pipewire_stream_closed),
layout_manager);
g_signal_connect (surface_context->pipewire_stream, "video-resized",
G_CALLBACK (on_pipewire_stream_video_resized),
layout_manager);
g_hash_table_remove (layout_manager->pending_streams,
GUINT_TO_POINTER (stream_id));
g_assert (g_hash_table_size (layout_manager->pending_streams) <
g_hash_table_size (layout_manager->pending_video_sizes));
g_assert (g_hash_table_size (layout_manager->pending_video_sizes) > 0);
if (g_hash_table_size (layout_manager->pending_streams) == 0)
transition_to_state (layout_manager, UPDATE_STATE_AWAIT_VIDEO_SIZES);
}
static void
grd_rdp_layout_manager_on_stream_created (GrdRdpStreamOwner *stream_owner,
uint32_t stream_id,
GrdStream *stream)
{
GrdRdpLayoutManager *layout_manager = GRD_RDP_LAYOUT_MANAGER (stream_owner);
SurfaceContext *surface_context = NULL;
g_assert (layout_manager->state == UPDATE_STATE_FATAL_ERROR ||
layout_manager->state == UPDATE_STATE_AWAIT_STREAMS);
if (!g_hash_table_lookup_extended (layout_manager->surface_table,
GUINT_TO_POINTER (stream_id),
NULL, (gpointer *) &surface_context))
g_assert_not_reached ();
g_assert (!surface_context->stream);
surface_context->stream = stream;
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR)
return;
g_signal_connect (stream, "ready", G_CALLBACK (on_stream_ready),
layout_manager);
}
static gboolean
is_extending_physical_desktop (GrdRdpLayoutManager *layout_manager)
{
GrdSession *session = GRD_SESSION (layout_manager->session_rdp);
GrdContext *context = grd_session_get_context (session);
GrdSettings *settings = grd_context_get_settings (context);
GrdRdpScreenShareMode screen_share_mode =
grd_session_rdp_get_screen_share_mode (layout_manager->session_rdp);
return GRD_IS_SETTINGS_USER (settings) &&
screen_share_mode == GRD_RDP_SCREEN_SHARE_MODE_EXTEND;
}
static void
hide_default_cursor (GrdRdpLayoutManager *layout_manager)
{
GrdRdpCursorRenderer *cursor_renderer =
grd_session_rdp_get_cursor_renderer (layout_manager->session_rdp);
GrdRdpCursorUpdate *cursor_update;
cursor_update = g_new0 (GrdRdpCursorUpdate, 1);
cursor_update->update_type = GRD_RDP_CURSOR_UPDATE_TYPE_HIDDEN;
grd_rdp_cursor_renderer_submit_cursor_update (cursor_renderer,
cursor_update);
}
void
grd_rdp_layout_manager_notify_session_started (GrdRdpLayoutManager *layout_manager)
{
layout_manager->session_started = TRUE;
if (is_extending_physical_desktop (layout_manager))
hide_default_cursor (layout_manager);
g_source_set_ready_time (layout_manager->layout_update_source, 0);
}
void
grd_rdp_layout_manager_submit_new_monitor_config (GrdRdpLayoutManager *layout_manager,
GrdRdpMonitorConfig *monitor_config)
{
g_assert (monitor_config);
if (!monitor_config->is_virtual)
g_assert (monitor_config->monitor_count == 1);
g_mutex_lock (&layout_manager->monitor_config_mutex);
g_clear_pointer (&layout_manager->pending_monitor_config, grd_rdp_monitor_config_free);
layout_manager->pending_monitor_config = monitor_config;
g_mutex_unlock (&layout_manager->monitor_config_mutex);
g_source_set_ready_time (layout_manager->layout_update_source, 0);
}
gboolean
grd_rdp_layout_manager_transform_position (GrdRdpLayoutManager *layout_manager,
uint32_t x,
uint32_t y,
GrdStream **stream,
GrdEventMotionAbs *motion_abs)
{
g_autoptr (GMutexLocker) locker = NULL;
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
locker = g_mutex_locker_new (&layout_manager->state_mutex);
if (layout_manager->state != UPDATE_STATE_AWAIT_CONFIG)
return FALSE;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
GrdRdpSurface *rdp_surface = surface_context->rdp_surface;
uint32_t surface_width = grd_rdp_surface_get_width (rdp_surface);
uint32_t surface_height = grd_rdp_surface_get_height (rdp_surface);
if (!surface_context->stream)
continue;
if (x < surface_context->output_origin_x ||
y < surface_context->output_origin_y ||
x >= surface_context->output_origin_x + surface_width ||
y >= surface_context->output_origin_y + surface_height)
continue;
*stream = g_object_ref (surface_context->stream);
motion_abs->input_rect_width = surface_width;
motion_abs->input_rect_height = surface_height;
motion_abs->x = x - surface_context->output_origin_x;
motion_abs->y = y - surface_context->output_origin_y;
return TRUE;
}
return FALSE;
}
static void
on_inhibition_done (GrdRdpRenderer *renderer,
GrdRdpLayoutManager *layout_manager)
{
g_source_set_ready_time (layout_manager->preparation_source, 0);
}
GrdRdpLayoutManager *
grd_rdp_layout_manager_new (GrdSessionRdp *session_rdp)
{
GrdRdpRenderer *renderer = grd_session_rdp_get_renderer (session_rdp);
GrdRdpLayoutManager *layout_manager;
layout_manager = g_object_new (GRD_TYPE_RDP_LAYOUT_MANAGER, NULL);
layout_manager->session_rdp = session_rdp;
layout_manager->inhibition_done_id =
g_signal_connect (renderer, "inhibition-done",
G_CALLBACK (on_inhibition_done),
layout_manager);
return layout_manager;
}
static void
grd_rdp_layout_manager_dispose (GObject *object)
{
GrdRdpLayoutManager *layout_manager = GRD_RDP_LAYOUT_MANAGER (object);
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
if (layout_manager->surface_table)
g_hash_table_remove_all (layout_manager->surface_table);
g_clear_signal_handler (&layout_manager->inhibition_done_id, renderer);
g_clear_handle_id (&layout_manager->layout_recreation_id, g_source_remove);
if (layout_manager->preparation_source)
{
g_source_destroy (layout_manager->preparation_source);
g_clear_pointer (&layout_manager->preparation_source, g_source_unref);
}
if (layout_manager->layout_update_source)
{
g_source_destroy (layout_manager->layout_update_source);
g_clear_pointer (&layout_manager->layout_update_source, g_source_unref);
}
g_clear_pointer (&layout_manager->current_monitor_config,
grd_rdp_monitor_config_free);
g_clear_pointer (&layout_manager->pending_monitor_config,
grd_rdp_monitor_config_free);
g_clear_pointer (&layout_manager->pending_video_sizes, g_hash_table_unref);
g_clear_pointer (&layout_manager->pending_streams, g_hash_table_unref);
g_clear_pointer (&layout_manager->surface_table, g_hash_table_unref);
G_OBJECT_CLASS (grd_rdp_layout_manager_parent_class)->dispose (object);
}
static void
grd_rdp_layout_manager_finalize (GObject *object)
{
GrdRdpLayoutManager *layout_manager = GRD_RDP_LAYOUT_MANAGER (object);
g_mutex_clear (&layout_manager->monitor_config_mutex);
g_mutex_clear (&layout_manager->state_mutex);
G_OBJECT_CLASS (grd_rdp_layout_manager_parent_class)->finalize (object);
}
static gboolean
maybe_pick_up_queued_monitor_config (GrdRdpLayoutManager *layout_manager)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&layout_manager->monitor_config_mutex);
if (!layout_manager->pending_monitor_config)
return FALSE;
if (layout_manager->current_monitor_config)
{
g_assert (layout_manager->current_monitor_config->is_virtual ==
layout_manager->pending_monitor_config->is_virtual);
}
g_clear_pointer (&layout_manager->current_monitor_config,
grd_rdp_monitor_config_free);
layout_manager->current_monitor_config =
g_steal_pointer (&layout_manager->pending_monitor_config);
g_clear_handle_id (&layout_manager->layout_recreation_id, g_source_remove);
return TRUE;
}
static void
dispose_physical_monitor_streams (GrdRdpLayoutManager *layout_manager)
{
GrdRdpMonitorConfig *monitor_config = layout_manager->current_monitor_config;
/*
* Monitor configs containing physical monitors cannot be mixed in sessions
* with monitor configs containing virtual monitors and vice versa.
*/
if (monitor_config->is_virtual)
return;
g_hash_table_remove_all (layout_manager->surface_table);
}
static void
dispose_unneeded_surfaces (GrdRdpLayoutManager *layout_manager)
{
GrdRdpMonitorConfig *monitor_config = layout_manager->current_monitor_config;
uint32_t n_target_surfaces = monitor_config->monitor_count;
uint32_t n_surfaces_to_dispose;
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
dispose_physical_monitor_streams (layout_manager);
if (g_hash_table_size (layout_manager->surface_table) <= n_target_surfaces)
return;
n_surfaces_to_dispose = g_hash_table_size (layout_manager->surface_table) -
n_target_surfaces;
g_debug ("[RDP] Layout manager: Disposing %u virtual monitor(s)",
n_surfaces_to_dispose);
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context) &&
n_surfaces_to_dispose > 0)
{
g_hash_table_iter_remove (&iter);
--n_surfaces_to_dispose;
}
g_assert (g_hash_table_size (layout_manager->surface_table) <= n_target_surfaces);
g_assert (n_surfaces_to_dispose == 0);
}
static gboolean
prepare_surface_contexts (GrdRdpLayoutManager *layout_manager,
GError **error)
{
GrdRdpMonitorConfig *monitor_config = layout_manager->current_monitor_config;
uint32_t n_target_surfaces = monitor_config->monitor_count;
SurfaceContext *surface_context = NULL;
GHashTableIter iter;
uint32_t i = 0;
while (g_hash_table_size (layout_manager->surface_table) < n_target_surfaces)
{
GrdRdpStreamOwner *stream_owner = GRD_RDP_STREAM_OWNER (layout_manager);
uint32_t stream_id;
surface_context = surface_context_new (layout_manager, error);
if (!surface_context)
return FALSE;
stream_id = grd_session_rdp_acquire_stream_id (layout_manager->session_rdp,
stream_owner);
surface_context->stream_id = stream_id;
g_hash_table_insert (layout_manager->surface_table,
GUINT_TO_POINTER (stream_id), surface_context);
}
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_context))
{
g_autofree GrdRdpSurfaceMapping *surface_mapping = NULL;
grd_rdp_surface_set_size (surface_context->rdp_surface, 0, 0);
g_clear_pointer (&surface_context->virtual_monitor, g_free);
if (monitor_config->is_virtual)
{
GrdRdpVirtualMonitor *virtual_monitor;
int32_t surface_pos_x;
int32_t surface_pos_y;
virtual_monitor = &monitor_config->virtual_monitors[i++];
g_debug ("[RDP] Layout manager: Preparing virtual monitor with "
"geometry [x, y, w, h] = [%i, %i, %u, %u]",
virtual_monitor->pos_x, virtual_monitor->pos_y,
virtual_monitor->width, virtual_monitor->height);
surface_pos_x = virtual_monitor->pos_x - monitor_config->layout_offset_x;
surface_pos_y = virtual_monitor->pos_y - monitor_config->layout_offset_y;
g_assert (surface_pos_x >= 0);
g_assert (surface_pos_y >= 0);
surface_context->output_origin_x = surface_pos_x;
surface_context->output_origin_y = surface_pos_y;
surface_context->virtual_monitor =
g_memdup2 (virtual_monitor, sizeof (GrdRdpVirtualMonitor));
}
else
{
surface_context->connector = monitor_config->connectors[i++];
}
surface_mapping = g_new0 (GrdRdpSurfaceMapping, 1);
surface_mapping->mapping_type = GRD_RDP_SURFACE_MAPPING_TYPE_MAP_TO_OUTPUT;
surface_mapping->output_origin_x = surface_context->output_origin_x;
surface_mapping->output_origin_y = surface_context->output_origin_y;
grd_rdp_surface_set_mapping (surface_context->rdp_surface,
g_steal_pointer (&surface_mapping));
}
return TRUE;
}
static void
update_stream_params (SurfaceContext *surface_context,
uint32_t stream_id)
{
GrdRdpLayoutManager *layout_manager = surface_context->layout_manager;
g_assert (surface_context->stream);
g_assert (surface_context->pipewire_stream);
g_hash_table_add (layout_manager->pending_video_sizes,
GUINT_TO_POINTER (stream_id));
grd_rdp_pipewire_stream_resize (surface_context->pipewire_stream,
surface_context->virtual_monitor);
}
static void
create_stream (SurfaceContext *surface_context,
uint32_t stream_id)
{
GrdRdpLayoutManager *layout_manager = surface_context->layout_manager;
GrdSession *session = GRD_SESSION (layout_manager->session_rdp);
g_assert (surface_context->virtual_monitor || surface_context->connector);
g_hash_table_add (layout_manager->pending_streams,
GUINT_TO_POINTER (stream_id));
g_hash_table_add (layout_manager->pending_video_sizes,
GUINT_TO_POINTER (stream_id));
if (surface_context->virtual_monitor)
{
GrdScreenCastCursorMode cursor_mode;
if (is_extending_physical_desktop (layout_manager))
cursor_mode = GRD_SCREEN_CAST_CURSOR_MODE_EMBEDDED;
else
cursor_mode = GRD_SCREEN_CAST_CURSOR_MODE_METADATA;
grd_session_record_virtual (session, stream_id, cursor_mode, TRUE);
}
else
{
grd_session_record_monitor (session, stream_id,
surface_context->connector,
GRD_SCREEN_CAST_CURSOR_MODE_METADATA);
}
}
static UpdateState
create_or_update_streams (GrdRdpLayoutManager *layout_manager)
{
GrdRdpSessionMetrics *session_metrics =
grd_session_rdp_get_session_metrics (layout_manager->session_rdp);
GrdRdpMonitorConfig *monitor_config = layout_manager->current_monitor_config;
SurfaceContext *surface_context = NULL;
GList *surfaces = NULL;
GHashTableIter iter;
gpointer key;
g_hash_table_iter_init (&iter, layout_manager->surface_table);
while (g_hash_table_iter_next (&iter, &key, (gpointer *) &surface_context))
{
uint32_t stream_id = GPOINTER_TO_UINT (key);
if (surface_context->stream)
update_stream_params (surface_context, stream_id);
else
create_stream (surface_context, stream_id);
surfaces = g_list_prepend (surfaces, surface_context->rdp_surface);
}
grd_rdp_session_metrics_prepare_surface_metrics (session_metrics, surfaces);
g_list_free (surfaces);
if (monitor_config->is_virtual)
{
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
update_monitor_data (layout_manager);
grd_rdp_renderer_notify_new_desktop_layout (renderer,
monitor_config->desktop_width,
monitor_config->desktop_height);
}
if (g_hash_table_size (layout_manager->pending_streams) > 0)
return UPDATE_STATE_AWAIT_STREAMS;
if (g_hash_table_size (layout_manager->pending_video_sizes) > 0)
return UPDATE_STATE_AWAIT_VIDEO_SIZES;
return UPDATE_STATE_START_RENDERING;
}
static gboolean
update_monitor_layout (gpointer user_data)
{
GrdRdpLayoutManager *layout_manager = user_data;
GrdRdpRenderer *renderer =
grd_session_rdp_get_renderer (layout_manager->session_rdp);
g_autoptr (GError) error = NULL;
if (!layout_manager->session_started)
return G_SOURCE_CONTINUE;
switch (layout_manager->state)
{
case UPDATE_STATE_FATAL_ERROR:
break;
case UPDATE_STATE_AWAIT_CONFIG:
if (maybe_pick_up_queued_monitor_config (layout_manager))
{
grd_rdp_renderer_inhibit_rendering (renderer);
transition_to_state (layout_manager, UPDATE_STATE_AWAIT_INHIBITION_DONE);
return G_SOURCE_CONTINUE;
}
break;
case UPDATE_STATE_AWAIT_INHIBITION_DONE:
break;
case UPDATE_STATE_PREPARE_SURFACES:
dispose_unneeded_surfaces (layout_manager);
if (!prepare_surface_contexts (layout_manager, &error))
{
g_warning ("[RDP] Layout manager: Failed to prepare surface contexts: %s",
error->message);
transition_to_state (layout_manager, UPDATE_STATE_FATAL_ERROR);
return G_SOURCE_CONTINUE;
}
transition_to_state (layout_manager,
create_or_update_streams (layout_manager));
break;
case UPDATE_STATE_AWAIT_STREAMS:
case UPDATE_STATE_AWAIT_VIDEO_SIZES:
break;
case UPDATE_STATE_START_RENDERING:
grd_rdp_renderer_uninhibit_rendering (renderer);
transition_to_state (layout_manager, UPDATE_STATE_AWAIT_CONFIG);
break;
}
return G_SOURCE_CONTINUE;
}
static gboolean
finish_layout_change_preparation (gpointer user_data)
{
GrdRdpLayoutManager *layout_manager = user_data;
GrdRdpSessionMetrics *session_metrics =
grd_session_rdp_get_session_metrics (layout_manager->session_rdp);
g_assert (layout_manager->state == UPDATE_STATE_FATAL_ERROR ||
layout_manager->state == UPDATE_STATE_AWAIT_INHIBITION_DONE);
if (layout_manager->state == UPDATE_STATE_FATAL_ERROR)
return G_SOURCE_CONTINUE;
grd_rdp_session_metrics_notify_layout_change (session_metrics);
transition_to_state (layout_manager, UPDATE_STATE_PREPARE_SURFACES);
return G_SOURCE_CONTINUE;
}
static gboolean
source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_source_set_ready_time (source, -1);
return callback (user_data);
}
static GSourceFuncs source_funcs =
{
.dispatch = source_dispatch,
};
static void
grd_rdp_layout_manager_init (GrdRdpLayoutManager *layout_manager)
{
GSource *layout_update_source;
GSource *preparation_source;
layout_manager->state = UPDATE_STATE_AWAIT_CONFIG;
layout_manager->surface_table =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) surface_context_free);
layout_manager->pending_streams = g_hash_table_new (NULL, NULL);
layout_manager->pending_video_sizes = g_hash_table_new (NULL, NULL);
g_mutex_init (&layout_manager->state_mutex);
g_mutex_init (&layout_manager->monitor_config_mutex);
layout_update_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (layout_update_source, update_monitor_layout,
layout_manager, NULL);
g_source_set_ready_time (layout_update_source, -1);
g_source_attach (layout_update_source, NULL);
layout_manager->layout_update_source = layout_update_source;
preparation_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (preparation_source, finish_layout_change_preparation,
layout_manager, NULL);
g_source_set_ready_time (preparation_source, -1);
g_source_attach (preparation_source, NULL);
layout_manager->preparation_source = preparation_source;
}
static void
grd_rdp_layout_manager_class_init (GrdRdpLayoutManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdRdpStreamOwnerClass *stream_owner_class =
GRD_RDP_STREAM_OWNER_CLASS (klass);
object_class->dispose = grd_rdp_layout_manager_dispose;
object_class->finalize = grd_rdp_layout_manager_finalize;
stream_owner_class->on_stream_created =
grd_rdp_layout_manager_on_stream_created;
}