mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
1044 lines
34 KiB
C
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;
|
|
}
|