This commit is contained in:
2026-02-13 13:06:50 +09:00
commit b54066842b
249 changed files with 69547 additions and 0 deletions

62
grd-avc-frame-info.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2024 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-avc-frame-info.h"
struct _GrdAVCFrameInfo
{
GrdAVCFrameType frame_type;
uint8_t qp;
uint8_t quality;
};
gboolean
grd_avc_frame_info_get_frame_type (GrdAVCFrameInfo *avc_frame_info)
{
return avc_frame_info->frame_type;
}
uint8_t
grd_avc_frame_info_get_qp (GrdAVCFrameInfo *avc_frame_info)
{
return avc_frame_info->qp;
}
uint8_t
grd_avc_frame_info_get_quality_value (GrdAVCFrameInfo *avc_frame_info)
{
return avc_frame_info->quality;
}
GrdAVCFrameInfo *
grd_avc_frame_info_new (GrdAVCFrameType frame_type,
uint8_t qp,
uint8_t quality)
{
GrdAVCFrameInfo *avc_frame_info;
avc_frame_info = g_new0 (GrdAVCFrameInfo, 1);
avc_frame_info->frame_type = frame_type;
avc_frame_info->qp = qp;
avc_frame_info->quality = quality;
return avc_frame_info;
}

41
grd-avc-frame-info.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <glib.h>
#include <stdint.h>
#include "grd-types.h"
typedef enum
{
GRD_AVC_FRAME_TYPE_P,
GRD_AVC_FRAME_TYPE_I,
} GrdAVCFrameType;
GrdAVCFrameInfo *grd_avc_frame_info_new (GrdAVCFrameType frame_type,
uint8_t qp,
uint8_t quality);
gboolean grd_avc_frame_info_get_frame_type (GrdAVCFrameInfo *avc_frame_info);
uint8_t grd_avc_frame_info_get_qp (GrdAVCFrameInfo *avc_frame_info);
uint8_t grd_avc_frame_info_get_quality_value (GrdAVCFrameInfo *avc_frame_info);

79
grd-bitstream.c Normal file
View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2024 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-bitstream.h"
#include <glib.h>
struct _GrdBitstream
{
uint8_t *data;
uint32_t data_size;
GrdAVCFrameInfo *avc_frame_info;
};
uint8_t *
grd_bitstream_get_data (GrdBitstream *bitstream)
{
return bitstream->data;
}
uint32_t
grd_bitstream_get_data_size (GrdBitstream *bitstream)
{
return bitstream->data_size;
}
GrdAVCFrameInfo *
grd_bitstream_get_avc_frame_info (GrdBitstream *bitstream)
{
return bitstream->avc_frame_info;
}
void
grd_bitstream_set_avc_frame_info (GrdBitstream *bitstream,
GrdAVCFrameInfo *avc_frame_info)
{
g_assert (!bitstream->avc_frame_info);
bitstream->avc_frame_info = avc_frame_info;
}
GrdBitstream *
grd_bitstream_new (uint8_t *data,
uint32_t data_size)
{
GrdBitstream *bitstream;
bitstream = g_new0 (GrdBitstream, 1);
bitstream->data = data;
bitstream->data_size = data_size;
return bitstream;
}
void
grd_bitstream_free (GrdBitstream *bitstream)
{
g_clear_pointer (&bitstream->avc_frame_info, g_free);
g_free (bitstream);
}

38
grd-bitstream.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <stdint.h>
#include "grd-types.h"
GrdBitstream *grd_bitstream_new (uint8_t *data,
uint32_t data_size);
void grd_bitstream_free (GrdBitstream *bitstream);
uint8_t *grd_bitstream_get_data (GrdBitstream *bitstream);
uint32_t grd_bitstream_get_data_size (GrdBitstream *bitstream);
GrdAVCFrameInfo *grd_bitstream_get_avc_frame_info (GrdBitstream *bitstream);
void grd_bitstream_set_avc_frame_info (GrdBitstream *bitstream,
GrdAVCFrameInfo *avc_frame_info);

2674
grd-clipboard-rdp.c Normal file

File diff suppressed because it is too large Load Diff

54
grd-clipboard-rdp.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include <freerdp/server/cliprdr.h>
#include "grd-clipboard.h"
#define GRD_TYPE_CLIPBOARD_RDP (grd_clipboard_rdp_get_type ())
G_DECLARE_FINAL_TYPE (GrdClipboardRdp,
grd_clipboard_rdp,
GRD, CLIPBOARD_RDP,
GrdClipboard)
GrdClipboardRdp *grd_clipboard_rdp_new (GrdSessionRdp *session_rdp,
HANDLE vcm,
gboolean relieve_filename_restriction);
void grd_clipboard_rdp_lock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp,
uint32_t clip_data_id);
void grd_clipboard_rdp_unlock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp,
uint32_t clip_data_id);
void grd_clipboard_rdp_request_remote_file_size_async (GrdClipboardRdp *clipboard_rdp,
uint32_t stream_id,
uint32_t list_index,
gboolean has_clip_data_id,
uint32_t clip_data_id);
void grd_clipboard_rdp_request_remote_file_range_async (GrdClipboardRdp *clipboard_rdp,
uint32_t stream_id,
uint32_t list_index,
uint64_t offset,
uint32_t requested_size,
gboolean has_clip_data_id,
uint32_t clip_data_id);

208
grd-clipboard-vnc.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2020-2021 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-clipboard-vnc.h"
#include "grd-session-vnc.h"
struct _GrdClipboardVnc
{
GrdClipboard parent;
GrdSessionVnc *session_vnc;
char *clipboard_utf8_string;
};
G_DEFINE_TYPE (GrdClipboardVnc, grd_clipboard_vnc, GRD_TYPE_CLIPBOARD)
static void
grd_clipboard_vnc_update_client_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_list)
{
GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard);
gboolean found_utf8_string = FALSE;
GrdMimeType mime_type;
GList *l;
for (l = mime_type_list; l && !found_utf8_string; l = l->next)
{
mime_type = GPOINTER_TO_UINT (l->data);
switch (mime_type)
{
case GRD_MIME_TYPE_TEXT_PLAIN:
break;
case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
case GRD_MIME_TYPE_TEXT_UTF8_STRING:
found_utf8_string = TRUE;
break;
case GRD_MIME_TYPE_TEXT_HTML:
case GRD_MIME_TYPE_IMAGE_BMP:
case GRD_MIME_TYPE_IMAGE_TIFF:
case GRD_MIME_TYPE_IMAGE_GIF:
case GRD_MIME_TYPE_IMAGE_JPEG:
case GRD_MIME_TYPE_IMAGE_PNG:
case GRD_MIME_TYPE_TEXT_URILIST:
case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
break;
default:
g_assert_not_reached ();
}
}
if (found_utf8_string)
{
g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free);
grd_clipboard_request_server_content_for_mime_type_async (clipboard,
mime_type);
}
g_list_free (mime_type_list);
}
static void
grd_clipboard_vnc_request_client_content_for_mime_type (GrdClipboard *clipboard,
GrdMimeTypeTable *mime_type_table,
unsigned int serial)
{
GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard);
uint32_t size;
size = strlen (clipboard_vnc->clipboard_utf8_string);
grd_clipboard_submit_client_content_for_mime_type (
clipboard, serial, (uint8_t *) clipboard_vnc->clipboard_utf8_string, size);
}
static void
grd_clipboard_vnc_submit_requested_server_content (GrdClipboard *clipboard,
uint8_t *src_data,
uint32_t src_size)
{
GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard);
g_autoptr (GError) error = NULL;
char *dst_data;
if (!src_data)
return;
dst_data = g_convert ((char *) src_data, src_size,
"iso8859-1", "utf-8",
NULL, NULL, &error);
if (!dst_data)
{
g_warning ("[VNC.Clipboard] Failed to convert clipboard content: %s",
error->message);
g_free (src_data);
return;
}
grd_session_vnc_set_client_clipboard_text (clipboard_vnc->session_vnc,
dst_data, strlen (dst_data));
g_free (src_data);
g_free (dst_data);
}
void
grd_clipboard_vnc_maybe_enable_clipboard (GrdClipboardVnc *clipboard_vnc)
{
grd_clipboard_maybe_enable_clipboard (GRD_CLIPBOARD (clipboard_vnc));
}
void
grd_clipboard_vnc_set_clipboard_text (GrdClipboardVnc *clipboard_vnc,
char *text,
int text_length)
{
GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_vnc);
g_autoptr (GError) error = NULL;
GrdMimeTypeTable *mime_type_table;
GList *mime_type_tables = NULL;
g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free);
clipboard_vnc->clipboard_utf8_string = g_convert (text, text_length,
"utf-8", "iso8859-1",
NULL, NULL, &error);
if (!clipboard_vnc->clipboard_utf8_string)
{
g_warning ("[VNC.Clipboard] Failed to convert clipboard content: %s",
error->message);
return;
}
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
mime_type_table->mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
mime_type_table->mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING;
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables);
}
GrdClipboardVnc *
grd_clipboard_vnc_new (GrdSessionVnc *session_vnc)
{
GrdClipboardVnc *clipboard_vnc;
clipboard_vnc = g_object_new (GRD_TYPE_CLIPBOARD_VNC, NULL);
clipboard_vnc->session_vnc = session_vnc;
grd_clipboard_initialize (GRD_CLIPBOARD (clipboard_vnc),
GRD_SESSION (session_vnc));
return clipboard_vnc;
}
static void
grd_clipboard_vnc_dispose (GObject *object)
{
GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (object);
grd_clipboard_disable_clipboard (GRD_CLIPBOARD (clipboard_vnc));
g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free);
G_OBJECT_CLASS (grd_clipboard_vnc_parent_class)->dispose (object);
}
static void
grd_clipboard_vnc_init (GrdClipboardVnc *clipboard_vnc)
{
}
static void
grd_clipboard_vnc_class_init (GrdClipboardVncClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass);
object_class->dispose = grd_clipboard_vnc_dispose;
clipboard_class->update_client_mime_type_list =
grd_clipboard_vnc_update_client_mime_type_list;
clipboard_class->request_client_content_for_mime_type =
grd_clipboard_vnc_request_client_content_for_mime_type;
clipboard_class->submit_requested_server_content =
grd_clipboard_vnc_submit_requested_server_content;
}

36
grd-clipboard-vnc.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include "grd-clipboard.h"
#define GRD_TYPE_CLIPBOARD_VNC (grd_clipboard_vnc_get_type ())
G_DECLARE_FINAL_TYPE (GrdClipboardVnc,
grd_clipboard_vnc,
GRD, CLIPBOARD_VNC,
GrdClipboard)
GrdClipboardVnc *grd_clipboard_vnc_new (GrdSessionVnc *session_vnc);
void grd_clipboard_vnc_maybe_enable_clipboard (GrdClipboardVnc *clipboard_vnc);
void grd_clipboard_vnc_set_clipboard_text (GrdClipboardVnc *clipboard_vnc,
char *text,
int text_length);

497
grd-clipboard.c Normal file
View File

@@ -0,0 +1,497 @@
/*
* Copyright (C) 2020-2021 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-clipboard.h"
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include "grd-session.h"
#define MAX_READ_TIME_MS 4000
typedef struct _ReadMimeTypeContentContext
{
GrdClipboard *clipboard;
int fd;
GCancellable *cancellable;
} ReadMimeTypeContentContext;
typedef struct _ReadMimeTypeContentResult
{
uint8_t *data;
uint32_t size;
} ReadMimeTypeContentResult;
typedef struct _GrdClipboardPrivate
{
GrdSession *session;
gboolean enabled;
GHashTable *client_mime_type_tables;
ReadMimeTypeContentResult *read_result;
GCancellable *read_cancellable;
unsigned int abort_read_source_id;
gboolean has_pending_read_operation;
GCond pending_read_cond;
GMutex pending_read_mutex;
} GrdClipboardPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GrdClipboard, grd_clipboard, G_TYPE_OBJECT)
static void
handle_read_result (GrdClipboard *clipboard,
ReadMimeTypeContentResult *read_result)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->has_pending_read_operation = FALSE;
/* Discard the read_result, if the clipboard is already disabled. */
if (!priv->enabled)
return;
if (read_result->data)
g_debug ("Clipboard[SelectionRead]: Request successful");
else
g_debug ("Clipboard[SelectionRead]: Request failed");
klass->submit_requested_server_content (clipboard, read_result->data,
read_result->size);
}
static void
flush_pending_read_result (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentResult *read_result;
g_mutex_lock (&priv->pending_read_mutex);
while (!priv->read_result)
g_cond_wait (&priv->pending_read_cond, &priv->pending_read_mutex);
g_mutex_unlock (&priv->pending_read_mutex);
read_result = g_steal_pointer (&priv->read_result);
handle_read_result (clipboard, read_result);
g_free (read_result);
}
static void
abort_current_read_operation (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
if (!priv->has_pending_read_operation)
return;
g_debug ("Clipboard[SelectionRead]: Aborting current read operation");
g_cancellable_cancel (priv->read_cancellable);
g_clear_object (&priv->read_cancellable);
g_clear_handle_id (&priv->abort_read_source_id, g_source_remove);
flush_pending_read_result (clipboard);
}
void
grd_clipboard_update_server_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_tables)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
GList *l;
g_debug ("Clipboard[SetSelection]: Updating servers clipboard");
for (l = mime_type_tables; l; l = l->next)
{
GrdMimeTypeTable *mime_type_table = l->data;
GrdMimeType mime_type;
mime_type = mime_type_table->mime_type;
g_debug ("Clipboard[SetSelection]: Update contains mime type %s",
grd_mime_type_to_string (mime_type));
g_hash_table_insert (priv->client_mime_type_tables,
GUINT_TO_POINTER (mime_type), mime_type_table);
}
if (!priv->enabled)
{
g_debug ("Clipboard[EnableClipboard]: Enabling clipboard");
priv->enabled = grd_session_enable_clipboard (priv->session,
clipboard, mime_type_tables);
if (priv->enabled)
g_debug ("Clipboard[EnableClipboard]: Clipboard enabled");
else
g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled");
}
else
{
abort_current_read_operation (clipboard);
if (mime_type_tables)
grd_session_set_selection (priv->session, mime_type_tables);
}
g_debug ("Clipboard[SetSelection]: Update complete");
g_list_free (mime_type_tables);
}
static void
async_read_operation_complete (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GrdClipboard *clipboard = user_data;
GrdClipboardPrivate *priv;
ReadMimeTypeContentContext *read_context =
g_task_get_task_data (G_TASK (result));
ReadMimeTypeContentResult *read_result;
if (g_cancellable_is_cancelled (read_context->cancellable))
return;
priv = grd_clipboard_get_instance_private (clipboard);
g_assert (priv->has_pending_read_operation);
g_clear_object (&priv->read_cancellable);
g_clear_handle_id (&priv->abort_read_source_id, g_source_remove);
read_result = g_steal_pointer (&priv->read_result);
handle_read_result (clipboard, read_result);
g_free (read_result);
}
static void
clear_read_context (gpointer data)
{
ReadMimeTypeContentContext *read_context = data;
g_object_unref (read_context->cancellable);
g_free (data);
}
static void
read_mime_type_content_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
ReadMimeTypeContentContext *read_context = task_data;
GrdClipboard *clipboard = read_context->clipboard;
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentResult *read_result;
GInputStream *input_stream;
GArray *data;
gboolean success = FALSE;
g_autoptr (GError) error = NULL;
input_stream = g_unix_input_stream_new (read_context->fd, TRUE);
data = g_array_new (FALSE, TRUE, sizeof (uint8_t));
while (TRUE)
{
int len;
uint8_t buffer[1024];
len = g_input_stream_read (input_stream, buffer, G_N_ELEMENTS (buffer),
read_context->cancellable, &error);
if (len < 0)
{
g_warning ("Clipboard[SelectionRead]: Failed to read mime type "
"content: %s", error->message);
break;
}
else if (len == 0)
{
success = TRUE;
break;
}
else
{
g_array_append_vals (data, buffer, len);
}
}
read_result = g_malloc0 (sizeof (ReadMimeTypeContentResult));
if (success && data->len > 0)
{
read_result->size = data->len;
read_result->data = (uint8_t *) g_array_free (data, FALSE);
}
else
{
g_array_free (data, TRUE);
}
g_object_unref (input_stream);
g_mutex_lock (&priv->pending_read_mutex);
priv->read_result = read_result;
g_cond_signal (&priv->pending_read_cond);
g_mutex_unlock (&priv->pending_read_mutex);
g_task_return_pointer (task, NULL, NULL);
}
static gboolean
abort_mime_type_content_read (gpointer user_data)
{
GrdClipboard *clipboard = user_data;
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_debug ("Clipboard[SelectionRead]: Aborting current read operation "
"(Timeout reached)");
g_assert (priv->has_pending_read_operation);
g_assert (priv->abort_read_source_id);
priv->abort_read_source_id = 0;
abort_current_read_operation (clipboard);
return G_SOURCE_REMOVE;
}
static void
read_mime_type_content_async (GrdClipboard *clipboard,
int fd)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentContext *read_context;
GTask *task;
abort_current_read_operation (clipboard);
priv->read_cancellable = g_cancellable_new ();
priv->has_pending_read_operation = TRUE;
g_assert (!priv->read_result);
read_context = g_malloc0 (sizeof (ReadMimeTypeContentContext));
read_context->clipboard = clipboard;
read_context->fd = fd;
read_context->cancellable = g_object_ref (priv->read_cancellable);
task = g_task_new (NULL, NULL, async_read_operation_complete, clipboard);
g_task_set_task_data (task, read_context, clear_read_context);
g_task_run_in_thread (task, read_mime_type_content_in_thread);
g_object_unref (task);
priv->abort_read_source_id =
g_timeout_add (MAX_READ_TIME_MS, abort_mime_type_content_read, clipboard);
}
void
grd_clipboard_request_server_content_for_mime_type_async (GrdClipboard *clipboard,
GrdMimeType mime_type)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
int fd;
g_return_if_fail (klass->submit_requested_server_content);
g_assert (priv->enabled);
g_debug ("Clipboard[SelectionRead]: Requesting data from servers clipboard"
" (mime type: %s)", grd_mime_type_to_string (mime_type));
fd = grd_session_selection_read (priv->session, mime_type);
if (fd == -1)
{
g_debug ("Clipboard[SelectionRead]: Request failed");
klass->submit_requested_server_content (clipboard, NULL, 0);
return;
}
read_mime_type_content_async (clipboard, fd);
}
void
grd_clipboard_initialize (GrdClipboard *clipboard,
GrdSession *session)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->session = session;
}
void
grd_clipboard_maybe_enable_clipboard (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_debug ("Clipboard[EnableClipboard]: Enabling clipboard");
if (priv->enabled)
{
g_debug ("Clipboard[EnableClipboard]: Clipboard already enabled");
return;
}
priv->enabled = grd_session_enable_clipboard (priv->session, clipboard, NULL);
if (priv->enabled)
g_debug ("Clipboard[EnableClipboard]: Clipboard enabled");
else
g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled");
}
void
grd_clipboard_disable_clipboard (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
if (!priv->enabled)
return;
g_debug ("Clipboard[DisableClipboard]: Disabling clipboard");
grd_session_disable_clipboard (priv->session);
priv->enabled = FALSE;
}
void
grd_clipboard_update_client_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_list)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
/**
* Ensure that the response with the read mime type content is sent to the
* client first, before sending the new mime type list
*/
abort_current_read_operation (clipboard);
g_assert (priv->enabled);
if (!klass->update_client_mime_type_list)
return;
g_hash_table_remove_all (priv->client_mime_type_tables);
g_debug ("Clipboard[SelectionOwnerChanged]: Updating clients clipboard");
klass->update_client_mime_type_list (clipboard, mime_type_list);
g_debug ("Clipboard[SelectionOwnerChanged]: Update complete");
}
void
grd_clipboard_submit_client_content_for_mime_type (GrdClipboard *clipboard,
unsigned int serial,
const uint8_t *data,
uint32_t size)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_assert (priv->enabled);
if (data && size)
g_debug ("Clipboard[SelectionTransfer]: Request for serial %u was successful", serial);
else
g_debug ("Clipboard[SelectionTransfer]: Request for serial %u failed", serial);
grd_session_selection_write (priv->session, serial, data, size);
}
void
grd_clipboard_request_client_content_for_mime_type (GrdClipboard *clipboard,
GrdMimeType mime_type,
unsigned int serial)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
GrdMimeTypeTable *mime_type_table = NULL;
g_assert (priv->enabled);
g_return_if_fail (klass->request_client_content_for_mime_type);
g_debug ("Clipboard[SelectionTransfer]: Requesting data from clients clipboard"
" (mime type: %s, serial: %u)",
grd_mime_type_to_string (mime_type), serial);
mime_type_table = g_hash_table_lookup (priv->client_mime_type_tables,
GUINT_TO_POINTER (mime_type));
if (!mime_type_table)
{
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
NULL, 0);
return;
}
klass->request_client_content_for_mime_type (clipboard, mime_type_table,
serial);
}
static void
free_mime_type_table (gpointer data)
{
GrdMimeTypeTable *mime_type_table = data;
g_free (mime_type_table);
}
static void
grd_clipboard_dispose (GObject *object)
{
GrdClipboard *clipboard = GRD_CLIPBOARD (object);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
abort_current_read_operation (clipboard);
g_clear_pointer (&priv->client_mime_type_tables, g_hash_table_destroy);
G_OBJECT_CLASS (grd_clipboard_parent_class)->dispose (object);
}
static void
grd_clipboard_finalize (GObject *object)
{
GrdClipboard *clipboard = GRD_CLIPBOARD (object);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_mutex_clear (&priv->pending_read_mutex);
g_cond_clear (&priv->pending_read_cond);
G_OBJECT_CLASS (grd_clipboard_parent_class)->finalize (object);
}
static void
grd_clipboard_init (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->client_mime_type_tables = g_hash_table_new_full (NULL, NULL, NULL,
free_mime_type_table);
g_cond_init (&priv->pending_read_cond);
g_mutex_init (&priv->pending_read_mutex);
}
static void
grd_clipboard_class_init (GrdClipboardClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_clipboard_dispose;
object_class->finalize = grd_clipboard_finalize;
}

68
grd-clipboard.h Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2020-2021 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#include "grd-mime-type.h"
#include "grd-types.h"
#define GRD_TYPE_CLIPBOARD (grd_clipboard_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdClipboard, grd_clipboard, GRD, CLIPBOARD, GObject)
struct _GrdClipboardClass
{
GObjectClass parent_class;
void (*update_client_mime_type_list) (GrdClipboard *clipboard,
GList *mime_type_list);
void (*request_client_content_for_mime_type) (GrdClipboard *clipboard,
GrdMimeTypeTable *mime_type_table,
unsigned int serial);
void (*submit_requested_server_content) (GrdClipboard *clipboard,
uint8_t *data,
uint32_t size);
};
void grd_clipboard_update_server_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_tables);
void grd_clipboard_request_server_content_for_mime_type_async (GrdClipboard *clipboard,
GrdMimeType mime_type);
void grd_clipboard_initialize (GrdClipboard *clipboard,
GrdSession *session);
void grd_clipboard_maybe_enable_clipboard (GrdClipboard *clipboard);
void grd_clipboard_disable_clipboard (GrdClipboard *clipboard);
void grd_clipboard_update_client_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_list);
void grd_clipboard_submit_client_content_for_mime_type (GrdClipboard *clipboard,
unsigned int serial,
const uint8_t *data,
uint32_t size);
void grd_clipboard_request_client_content_for_mime_type (GrdClipboard *clipboard,
GrdMimeType mime_type,
unsigned int serial);

730
grd-configuration.c Normal file
View File

@@ -0,0 +1,730 @@
/*
* Copyright (C) 2024 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#include "config.h"
#include <gio/gio.h>
#include <glib-unix.h>
#include <glib/gstdio.h>
#include <glib.h>
#include <polkit/polkit.h>
#ifdef HAVE_RDP
#include <freerdp/freerdp.h>
#endif
#include "grd-dbus-remote-desktop.h"
#include "grd-private.h"
#include "grd-settings-system.h"
#include "grd-utils.h"
#define GRD_CONFIGURATION_TIMEOUT_S 10
#define GRD_SYSTEMD_SERVICE "gnome-remote-desktop.service"
#define GRD_SERVER_USER_CERT_SUBDIR "certificates"
#define GRD_CONFIGURE_SYSTEM_DAEMON_POLKIT_ACTION "org.gnome.remotedesktop.configure-system-daemon"
#define GRD_MAX_CERTIFICATE_FILE_SIZE_BYTES (50 * 1024)
#define GRD_MAX_PRIVATE_KEY_FILE_SIZE_BYTES (50 * 1024)
typedef struct
{
unsigned int *timeout_source_id;
GSourceFunc function;
gpointer data;
} TimeoutLocker;
struct _GrdConfiguration
{
GApplication parent;
PolkitAuthority *authority;
GrdSettingsSystem *settings;
GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server;
unsigned int own_name_source_id;
GDBusProxy *unit_proxy;
GrdSystemdUnitActiveState unit_state;
unsigned int timeout_source_id;
unsigned int sigint_source_id;
unsigned int sigterm_source_id;
};
#define GRD_TYPE_CONFIGURATION (grd_configuration_get_type ())
G_DECLARE_FINAL_TYPE (GrdConfiguration,
grd_configuration,
GRD, CONFIGURATION,
GApplication)
G_DEFINE_TYPE (GrdConfiguration, grd_configuration, G_TYPE_APPLICATION)
static void
timeout_locker_free (TimeoutLocker *locker);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (TimeoutLocker, timeout_locker_free)
#ifdef HAVE_RDP
G_DEFINE_AUTOPTR_CLEANUP_FUNC (rdpCertificate, freerdp_certificate_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (rdpPrivateKey, freerdp_key_free)
#endif
static TimeoutLocker *
timeout_locker_new (unsigned int *timeout_source_id,
GSourceFunc function,
gpointer data)
{
TimeoutLocker *locker = g_new0 (TimeoutLocker, 1);
locker->timeout_source_id = timeout_source_id;
locker->function = function;
locker->data = data;
g_clear_handle_id (locker->timeout_source_id, g_source_remove);
return locker;
}
static void
timeout_locker_free (TimeoutLocker *locker)
{
if (*locker->timeout_source_id == 0)
{
*locker->timeout_source_id =
g_timeout_add_seconds (GRD_CONFIGURATION_TIMEOUT_S,
locker->function,
locker->data);
}
g_free (locker);
}
static void
grd_configuration_init (GrdConfiguration *app)
{
}
static void
on_unit_active_state_changed (GrdConfiguration *configuration)
{
if (!grd_systemd_unit_get_active_state (configuration->unit_proxy,
&configuration->unit_state,
NULL))
return;
g_object_notify (G_OBJECT (configuration->settings), "rdp-enabled");
}
static void
watch_grd_system_unit_active_state (GrdConfiguration *configuration)
{
GDBusProxy *unit_proxy = NULL;
g_autoptr (GError) error = NULL;
if (!grd_systemd_get_unit (G_BUS_TYPE_SYSTEM,
GRD_SYSTEMD_SERVICE,
&unit_proxy,
&error))
{
g_warning ("Could not load %s: %s",
GRD_SYSTEMD_SERVICE, error->message);
return;
}
grd_systemd_unit_get_active_state (unit_proxy,
&configuration->unit_state,
NULL);
g_signal_connect_object (G_OBJECT (unit_proxy),
"g-properties-changed",
G_CALLBACK (on_unit_active_state_changed),
configuration,
G_CONNECT_SWAPPED);
configuration->unit_proxy = unit_proxy;
}
static gboolean
transform_enabled (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
GrdConfiguration *configuration = user_data;
gboolean enabled;
GrdSystemdUnitActiveState active_state;
enabled = g_value_get_boolean (from_value);
active_state = configuration->unit_state;
g_value_set_boolean (to_value,
enabled &&
(active_state == GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVE ||
active_state == GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVATING));
return TRUE;
}
static gboolean
on_handle_enable (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GrdConfiguration *configuration)
{
g_autoptr (GError) error = NULL;
if (!grd_toggle_systemd_unit (GRD_RUNTIME_MODE_SYSTEM, TRUE, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed enabling %s: %s",
GRD_SYSTEMD_SERVICE,
error->message);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
g_object_set (G_OBJECT (configuration->settings), "rdp-enabled", TRUE, NULL);
grd_dbus_remote_desktop_configuration_rdp_server_complete_enable (
configuration_rdp_server,
invocation);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
on_handle_disable (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GrdConfiguration *configuration)
{
g_autoptr (GError) error = NULL;
if (!grd_toggle_systemd_unit (GRD_RUNTIME_MODE_SYSTEM, FALSE, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed disabling %s: %s",
GRD_SYSTEMD_SERVICE,
error->message);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
g_object_set (G_OBJECT (configuration->settings), "rdp-enabled", FALSE, NULL);
grd_dbus_remote_desktop_configuration_rdp_server_complete_disable (
configuration_rdp_server,
invocation);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
on_handle_get_credentials (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GrdConfiguration *configuration)
{
g_autofree char *username = NULL;
g_autofree char *password = NULL;
g_autoptr (GError) error = NULL;
GVariantBuilder credentials;
g_variant_builder_init (&credentials, G_VARIANT_TYPE ("a{sv}"));
grd_settings_get_rdp_credentials (GRD_SETTINGS (configuration->settings),
&username, &password,
&error);
if (error)
g_warning ("[Configuration] Failed to get credentials: %s", error->message);
if (!username)
username = g_strdup ("");
if (!password)
password = g_strdup ("");
g_variant_builder_add (&credentials, "{sv}", "username",
g_variant_new_string (username));
g_variant_builder_add (&credentials, "{sv}", "password",
g_variant_new_string (password));
grd_dbus_remote_desktop_configuration_rdp_server_complete_get_credentials (
configuration_rdp_server,
invocation,
g_variant_builder_end (&credentials));
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
on_handle_set_credentials (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GVariant *credentials,
GrdConfiguration *configuration)
{
g_autofree char *old_username = NULL;
g_autofree char *old_password = NULL;
g_autofree char *username = NULL;
g_autofree char *password = NULL;
g_autoptr (GError) error = NULL;
g_variant_lookup (credentials, "username", "s", &username);
g_variant_lookup (credentials, "password", "s", &password);
if (!username && !password)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"Username or password expected "
"in credentials");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!username || !password)
{
grd_settings_get_rdp_credentials (GRD_SETTINGS (configuration->settings),
&old_username, &old_password,
NULL);
}
if (!username)
username = g_steal_pointer (&old_username);
if (!password)
password = g_steal_pointer (&old_password);
if (!grd_settings_set_rdp_credentials (GRD_SETTINGS (configuration->settings),
username, password,
&error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed to set credentials: %s",
error->message);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
grd_dbus_remote_desktop_configuration_rdp_server_complete_set_credentials (
configuration_rdp_server,
invocation);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
on_handle_import_certificate (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list,
GVariant *certificate,
GVariant *private_key,
GrdConfiguration *configuration)
{
g_autoptr (rdpCertificate) rdp_certificate = NULL;
g_autoptr (rdpPrivateKey) rdp_private_key = NULL;
g_autofree char *certificate_filename = NULL;
g_autofree char *key_filename = NULL;
g_autoptr (GError) error = NULL;
g_autofd int certificate_fd = -1;
g_autofd int key_fd = -1;
GFileTest fd_test_results;
int certificate_fd_index = -1;
int key_fd_index = -1;
gboolean success;
g_variant_get (certificate, "(sh)", &certificate_filename,
&certificate_fd_index);
g_variant_get (private_key, "(sh)", &key_filename,
&key_fd_index);
if (!G_IS_UNIX_FD_LIST (fd_list))
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire "
"file descriptor for certificate and private key: "
"The sender supplied an invalid fd list");
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (certificate_fd_index < 0 ||
certificate_fd_index >= g_unix_fd_list_get_length (fd_list))
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire "
"file descriptor for certificate: "
"The sender supplied an invalid fd index");
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (key_fd_index < 0 ||
key_fd_index >= g_unix_fd_list_get_length (fd_list))
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire "
"file descriptor for private key: "
"The sender supplied an invalid fd index");
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
certificate_fd = g_unix_fd_list_get (fd_list, certificate_fd_index, &error);
if (certificate_fd == -1)
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
success = grd_test_fd (certificate_fd, GRD_MAX_CERTIFICATE_FILE_SIZE_BYTES,
&fd_test_results, &error);
if (!success)
{
g_prefix_error (&error, "Could not inspect certificate file descriptor");
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!(fd_test_results & G_FILE_TEST_IS_REGULAR))
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR,
G_IO_ERROR_NOT_REGULAR_FILE,
"Invalid certificate file");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
key_fd = g_unix_fd_list_get (fd_list, key_fd_index, &error);
if (key_fd == -1)
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
success = grd_test_fd (key_fd, GRD_MAX_PRIVATE_KEY_FILE_SIZE_BYTES,
&fd_test_results, &error);
if (!success)
{
g_prefix_error (&error, "Could not inspect private key file descriptor");
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!(fd_test_results & G_FILE_TEST_IS_REGULAR))
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR,
G_IO_ERROR_NOT_REGULAR_FILE,
"Invalid private key file");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
grd_rewrite_path_to_user_data_dir (&certificate_filename,
GRD_SERVER_USER_CERT_SUBDIR,
"rdp-tls.crt");
if (!grd_write_fd_to_file (certificate_fd, certificate_filename,
NULL, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (certificate_filename)
rdp_certificate = freerdp_certificate_new_from_file (certificate_filename);
if (!rdp_certificate)
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Invalid certificate");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
grd_rewrite_path_to_user_data_dir (&key_filename,
GRD_SERVER_USER_CERT_SUBDIR,
"rdp-tls.key");
if (!grd_write_fd_to_file (key_fd, key_filename, NULL, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (key_filename)
rdp_private_key = freerdp_key_new_from_file (key_filename);
if (!rdp_private_key)
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Invalid private key");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
g_object_set (configuration->settings,
"rdp-server-cert-path", certificate_filename,
"rdp-server-key-path", key_filename,
NULL);
grd_dbus_remote_desktop_configuration_rdp_server_complete_import_certificate (
configuration_rdp_server,
invocation,
fd_list);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
terminate (gpointer user_data)
{
GrdConfiguration *configuration = user_data;
g_clear_handle_id (&configuration->timeout_source_id, g_source_remove);
g_clear_handle_id (&configuration->sigint_source_id, g_source_remove);
g_clear_handle_id (&configuration->sigterm_source_id, g_source_remove);
g_application_release (G_APPLICATION (configuration));
return G_SOURCE_REMOVE;
}
static gboolean
ensure_polkit_authority (GrdConfiguration *configuration,
GError **error)
{
if (configuration->authority)
return TRUE;
configuration->authority = polkit_authority_get_sync (NULL, error);
return configuration->authority != NULL;
}
static gboolean
on_authorize_method (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server,
GDBusMethodInvocation *invocation,
GrdConfiguration *configuration)
{
g_autoptr (PolkitAuthorizationResult) result = NULL;
g_autoptr (PolkitSubject) subject = NULL;
g_autoptr (TimeoutLocker) locker = NULL;
PolkitCheckAuthorizationFlags flags;
g_autoptr (GError) error = NULL;
const char *sender = NULL;
const char *action = NULL;
locker = timeout_locker_new (&configuration->timeout_source_id, terminate, configuration);
if (!ensure_polkit_authority (configuration, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Couldn't get polkit authority: %s",
error->message);
return FALSE;
}
sender = g_dbus_method_invocation_get_sender (invocation);
subject = polkit_system_bus_name_new (sender);
action = GRD_CONFIGURE_SYSTEM_DAEMON_POLKIT_ACTION;
flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION;
result = polkit_authority_check_authorization_sync (configuration->authority,
subject, action,
NULL, flags, NULL,
&error);
if (!result)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed to check authorization: %s",
error->message);
return FALSE;
}
if (!polkit_authorization_result_get_is_authorized (result))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Not authorized for action %s",
action);
return FALSE;
}
return TRUE;
}
static void
on_bus_acquired (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
GrdConfiguration *configuration = user_data;
g_debug ("[Configuration] Now on system bus");
g_dbus_interface_skeleton_export (
G_DBUS_INTERFACE_SKELETON (configuration->configuration_rdp_server),
connection,
REMOTE_DESKTOP_CONFIGURATION_OBJECT_PATH,
NULL);
}
static void
on_name_acquired (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
g_debug ("[Configuration] Owned %s name", name);
}
static void
on_name_lost (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
g_debug ("[Configuration] Lost owned %s name", name);
}
static void
register_signals (GrdConfiguration *configuration)
{
configuration->timeout_source_id =
g_timeout_add_seconds (GRD_CONFIGURATION_TIMEOUT_S,
terminate,
configuration);
configuration->sigint_source_id = g_unix_signal_add (SIGINT,
terminate,
configuration);
configuration->sigterm_source_id = g_unix_signal_add (SIGTERM,
terminate,
configuration);
}
static void
grd_configuration_startup (GApplication *application)
{
GrdConfiguration *configuration = GRD_CONFIGURATION (application);
configuration->settings = grd_settings_system_new ();
grd_settings_system_use_local_state (configuration->settings);
configuration->configuration_rdp_server =
grd_dbus_remote_desktop_configuration_rdp_server_skeleton_new ();
watch_grd_system_unit_active_state (configuration);
g_object_bind_property_full (configuration->settings, "rdp-enabled",
configuration->configuration_rdp_server, "enabled",
G_BINDING_SYNC_CREATE,
transform_enabled,
NULL,
configuration,
NULL);
g_object_bind_property (configuration->settings, "rdp-port",
configuration->configuration_rdp_server, "port",
G_BINDING_SYNC_CREATE);
g_object_bind_property (configuration->settings, "rdp-server-cert-path",
configuration->configuration_rdp_server, "tls-cert",
G_BINDING_SYNC_CREATE);
g_object_bind_property (configuration->settings, "rdp-server-fingerprint",
configuration->configuration_rdp_server, "tls-fingerprint",
G_BINDING_SYNC_CREATE);
g_object_bind_property (configuration->settings, "rdp-server-key-path",
configuration->configuration_rdp_server, "tls-key",
G_BINDING_SYNC_CREATE);
g_object_bind_property (configuration->settings, "rdp-auth-methods",
configuration->configuration_rdp_server, "auth-methods",
G_BINDING_SYNC_CREATE);
g_signal_connect_object (configuration->configuration_rdp_server, "handle-enable",
G_CALLBACK (on_handle_enable),
configuration, 0);
g_signal_connect_object (configuration->configuration_rdp_server, "handle-disable",
G_CALLBACK (on_handle_disable),
configuration, 0);
g_signal_connect_object (configuration->configuration_rdp_server, "handle-get-credentials",
G_CALLBACK (on_handle_get_credentials),
configuration, 0);
g_signal_connect_object (configuration->configuration_rdp_server, "handle-set-credentials",
G_CALLBACK (on_handle_set_credentials),
configuration, 0);
g_signal_connect_object (configuration->configuration_rdp_server, "handle-import-certificate",
G_CALLBACK (on_handle_import_certificate),
configuration, 0);
g_signal_connect_object (configuration->configuration_rdp_server, "g-authorize-method",
G_CALLBACK (on_authorize_method),
configuration, 0);
configuration->own_name_source_id =
g_bus_own_name (G_BUS_TYPE_SYSTEM,
REMOTE_DESKTOP_CONFIGURATION_BUS_NAME,
G_BUS_NAME_OWNER_FLAGS_NONE,
on_bus_acquired,
on_name_acquired,
on_name_lost,
configuration, NULL);
register_signals (configuration);
g_application_hold (application);
G_APPLICATION_CLASS (grd_configuration_parent_class)->startup (application);
}
static void
grd_configuration_shutdown (GApplication *application)
{
GrdConfiguration *configuration = GRD_CONFIGURATION (application);
g_clear_object (&configuration->authority);
g_clear_object (&configuration->settings);
g_clear_object (&configuration->unit_proxy);
g_dbus_interface_skeleton_unexport (
G_DBUS_INTERFACE_SKELETON (configuration->configuration_rdp_server));
g_clear_object (&configuration->configuration_rdp_server);
g_clear_handle_id (&configuration->own_name_source_id, g_bus_unown_name);
g_clear_handle_id (&configuration->timeout_source_id, g_source_remove);
g_clear_handle_id (&configuration->sigint_source_id, g_source_remove);
g_clear_handle_id (&configuration->sigterm_source_id, g_source_remove);
G_APPLICATION_CLASS (grd_configuration_parent_class)->shutdown (application);
}
static void
grd_configuration_class_init (GrdConfigurationClass *klass)
{
GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
g_application_class->startup = grd_configuration_startup;
g_application_class->shutdown = grd_configuration_shutdown;
}
int
main (int argc,
char **argv)
{
g_autoptr (GApplication) app = NULL;
app = g_object_new (grd_configuration_get_type (),
"application-id", REMOTE_DESKTOP_CONFIGURATION_BUS_NAME,
"flags", G_APPLICATION_IS_SERVICE,
NULL);
return g_application_run (app, argc, argv);
}

200
grd-context.c Normal file
View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2015 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#include "config.h"
#include "grd-context.h"
#include "grd-egl-thread.h"
#include "grd-settings-handover.h"
#include "grd-settings-headless.h"
#include "grd-settings-system.h"
#include "grd-settings-user.h"
#include "grd-dbus-mutter-remote-desktop.h"
#include "grd-dbus-mutter-screen-cast.h"
struct _GrdContext
{
GObject parent;
GrdDBusMutterRemoteDesktop *mutter_remote_desktop_proxy;
GrdDBusMutterScreenCast *mutter_screen_cast_proxy;
GrdEglThread *egl_thread;
GrdSettings *settings;
GrdRuntimeMode runtime_mode;
GrdDBusRemoteDesktopRdpServer *rdp_server_iface;
GrdDBusRemoteDesktopVncServer *vnc_server_iface;
};
G_DEFINE_TYPE (GrdContext, grd_context, G_TYPE_OBJECT)
GrdDBusMutterRemoteDesktop *
grd_context_get_mutter_remote_desktop_proxy (GrdContext *context)
{
return context->mutter_remote_desktop_proxy;
}
GrdDBusMutterScreenCast *
grd_context_get_mutter_screen_cast_proxy (GrdContext *context)
{
return context->mutter_screen_cast_proxy;
}
GrdDBusRemoteDesktopRdpServer *
grd_context_get_rdp_server_interface (GrdContext *context)
{
return context->rdp_server_iface;
}
GrdDBusRemoteDesktopVncServer *
grd_context_get_vnc_server_interface (GrdContext *context)
{
return context->vnc_server_iface;
}
void
grd_context_set_mutter_remote_desktop_proxy (GrdContext *context,
GrdDBusMutterRemoteDesktop *proxy)
{
g_clear_object (&context->mutter_remote_desktop_proxy);
context->mutter_remote_desktop_proxy = proxy;
}
void
grd_context_set_mutter_screen_cast_proxy (GrdContext *context,
GrdDBusMutterScreenCast *proxy)
{
g_clear_object (&context->mutter_screen_cast_proxy);
context->mutter_screen_cast_proxy = proxy;
}
void
grd_context_set_rdp_server_interface (
GrdContext *context,
GrdDBusRemoteDesktopRdpServer *rdp_server_iface)
{
g_clear_object (&context->rdp_server_iface);
context->rdp_server_iface = rdp_server_iface;
}
void
grd_context_set_vnc_server_interface (
GrdContext *context,
GrdDBusRemoteDesktopVncServer *vnc_server_iface)
{
g_clear_object (&context->vnc_server_iface);
context->vnc_server_iface = vnc_server_iface;
}
GrdSettings *
grd_context_get_settings (GrdContext *context)
{
return context->settings;
}
GrdEglThread *
grd_context_get_egl_thread (GrdContext *context)
{
return context->egl_thread;
}
GrdRuntimeMode
grd_context_get_runtime_mode (GrdContext *context)
{
return context->runtime_mode;
}
void
grd_context_notify_daemon_ready (GrdContext *context)
{
g_autoptr (GError) error = NULL;
if (context->egl_thread ||
context->runtime_mode == GRD_RUNTIME_MODE_SYSTEM)
return;
context->egl_thread = grd_egl_thread_new (&error);
if (!context->egl_thread)
g_debug ("Failed to create EGL thread: %s", error->message);
}
GrdContext *
grd_context_new (GrdRuntimeMode runtime_mode,
GError **error)
{
g_autoptr (GrdContext) context = NULL;
context = g_object_new (GRD_TYPE_CONTEXT, NULL);
context->runtime_mode = runtime_mode;
switch (runtime_mode)
{
case GRD_RUNTIME_MODE_SCREEN_SHARE:
context->settings = GRD_SETTINGS (grd_settings_user_new ());
break;
case GRD_RUNTIME_MODE_HEADLESS:
context->settings = GRD_SETTINGS (grd_settings_headless_new ());
break;
case GRD_RUNTIME_MODE_SYSTEM:
context->settings = GRD_SETTINGS (grd_settings_system_new ());
break;
case GRD_RUNTIME_MODE_HANDOVER:
context->settings = GRD_SETTINGS (grd_settings_handover_new ());
break;
}
if (!context->settings)
return NULL;
return g_steal_pointer (&context);
}
static void
grd_context_finalize (GObject *object)
{
GrdContext *context = GRD_CONTEXT (object);
g_clear_object (&context->mutter_remote_desktop_proxy);
g_clear_object (&context->mutter_screen_cast_proxy);
g_clear_pointer (&context->egl_thread, grd_egl_thread_free);
g_clear_object (&context->settings);
G_OBJECT_CLASS (grd_context_parent_class)->finalize (object);
}
static void
grd_context_init (GrdContext *context)
{
}
static void
grd_context_class_init (GrdContextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = grd_context_finalize;
}

65
grd-context.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2015 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#pragma once
#include <glib-object.h>
#include "grd-dbus-mutter-remote-desktop.h"
#include "grd-dbus-mutter-screen-cast.h"
#include "grd-dbus-remote-desktop.h"
#include "grd-settings.h"
#include "grd-types.h"
#define GRD_TYPE_CONTEXT (grd_context_get_type ())
G_DECLARE_FINAL_TYPE (GrdContext, grd_context, GRD, CONTEXT, GObject)
GrdContext * grd_context_new (GrdRuntimeMode runtime_mode,
GError **error);
GrdDBusMutterRemoteDesktop * grd_context_get_mutter_remote_desktop_proxy (GrdContext *context);
GrdDBusMutterScreenCast * grd_context_get_mutter_screen_cast_proxy (GrdContext *context);
GrdDBusRemoteDesktopRdpServer * grd_context_get_rdp_server_interface (GrdContext *context);
GrdDBusRemoteDesktopVncServer * grd_context_get_vnc_server_interface (GrdContext *context);
void grd_context_set_mutter_remote_desktop_proxy (GrdContext *context,
GrdDBusMutterRemoteDesktop *proxy);
void grd_context_set_mutter_screen_cast_proxy (GrdContext *context,
GrdDBusMutterScreenCast *proxy);
void grd_context_set_rdp_server_interface (GrdContext *context,
GrdDBusRemoteDesktopRdpServer *rdp_server_iface);
void grd_context_set_vnc_server_interface (GrdContext *context,
GrdDBusRemoteDesktopVncServer *vnc_server_iface);
GrdSettings * grd_context_get_settings (GrdContext *context);
GrdEglThread * grd_context_get_egl_thread (GrdContext *context);
GrdRuntimeMode grd_context_get_runtime_mode (GrdContext *context);
void grd_context_notify_daemon_ready (GrdContext *context);

104
grd-control.c Normal file
View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2015 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#include "config.h"
#include <gio/gio.h>
#include <stdio.h>
#include <string.h>
#include "grd-private.h"
int
main (int argc, char **argv)
{
g_autoptr(GApplication) app = NULL;
gboolean terminate = FALSE;
gboolean headless = FALSE;
gboolean system = FALSE;
gboolean handover = FALSE;
GOptionEntry entries[] = {
{ "terminate", 0, 0, G_OPTION_ARG_NONE, &terminate,
"Terminate the daemon", NULL },
{ "headless", 0, 0, G_OPTION_ARG_NONE, &headless,
"Control headless daemon", NULL },
#if defined(HAVE_RDP) && defined(HAVE_LIBSYSTEMD)
{ "system", 0, 0, G_OPTION_ARG_NONE, &system,
"Control system daemon", NULL },
{ "handover", 0, 0, G_OPTION_ARG_NONE, &handover,
"Control handover daemon", NULL },
#endif /* HAVE_RDP && HAVE_LIBSYSTEMD */
{ NULL }
};
GError *error = NULL;
GOptionContext *context;
const char *app_id;
context = g_option_context_new ("- control gnome-remote-desktop");
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("Invalid option: %s\n", error->message);
g_error_free (error);
return 1;
}
if (!terminate)
{
g_printerr ("%s", g_option_context_get_help (context, TRUE, NULL));
return 1;
}
if (headless)
app_id = GRD_DAEMON_HEADLESS_APPLICATION_ID;
else if (system)
app_id = GRD_DAEMON_SYSTEM_APPLICATION_ID;
else if (handover)
app_id = GRD_DAEMON_HANDOVER_APPLICATION_ID;
else
app_id = GRD_DAEMON_USER_APPLICATION_ID;
app = g_application_new (app_id, G_APPLICATION_DEFAULT_FLAGS);
if (!g_application_register (app, NULL, NULL))
{
g_warning ("Failed to register with application\n");
return 1;
}
if (!g_application_get_is_registered (app))
{
g_warning ("Not registered\n");
return 1;
}
if (!g_application_get_is_remote (app))
{
g_warning ("Failed to connect to application\n");
return 1;
}
if (terminate)
g_action_group_activate_action (G_ACTION_GROUP (app),
"terminate", NULL);
else
g_assert_not_reached ();
return 0;
}

208
grd-credentials-file.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2022 SUSE LLC
*
* 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.
*
* Written by:
* Alynx Zhou <alynx.zhou@gmail.com>
*/
#include "config.h"
#include "grd-credentials-file.h"
#include <gio/gio.h>
#define GRD_CREDENTIALS_FILE_KEY "credentials"
struct _GrdCredentialsFile
{
GrdCredentials parent;
char *filename;
GKeyFile *key_file;
};
G_DEFINE_TYPE (GrdCredentialsFile, grd_credentials_file, GRD_TYPE_CREDENTIALS)
static const char *
group_name_from_type (GrdCredentialsType type)
{
switch (type)
{
case GRD_CREDENTIALS_TYPE_RDP:
return "RDP";
case GRD_CREDENTIALS_TYPE_VNC:
return "VNC";
}
g_assert_not_reached ();
}
static gboolean
grd_credentials_file_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error)
{
GrdCredentialsFile *credentials_file = GRD_CREDENTIALS_FILE (credentials);
g_autofree const char *serialized = NULL;
g_autofree char *contents = NULL;
g_autoptr (GFile) file = NULL;
size_t length;
g_variant_ref_sink (variant);
serialized = g_variant_print (variant, TRUE);
g_variant_unref (variant);
g_key_file_set_string (credentials_file->key_file,
group_name_from_type (type),
GRD_CREDENTIALS_FILE_KEY, serialized);
contents = g_key_file_to_data (credentials_file->key_file, &length, error);
if (!contents)
return FALSE;
file = g_file_new_for_path (credentials_file->filename);
return g_file_replace_contents (file, contents, length, NULL, FALSE,
G_FILE_CREATE_REPLACE_DESTINATION |
G_FILE_CREATE_PRIVATE, NULL, NULL, error);
}
static GVariant *
grd_credentials_file_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
GrdCredentialsFile *credentials_file = GRD_CREDENTIALS_FILE (credentials);
g_autofree const char *serialized = NULL;
g_autoptr (GError) local_error = NULL;
serialized = g_key_file_get_string (credentials_file->key_file,
group_name_from_type (type),
GRD_CREDENTIALS_FILE_KEY, &local_error);
if (!serialized)
{
if (!g_error_matches (local_error,
G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND) &&
!g_error_matches (local_error,
G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
return g_variant_parse (NULL, serialized, NULL, NULL, error);
}
static gboolean
grd_credentials_file_clear (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
GrdCredentialsFile *credentials_file = GRD_CREDENTIALS_FILE (credentials);
g_autoptr (GError) local_error = NULL;
gboolean removed;
removed = g_key_file_remove_key (credentials_file->key_file,
group_name_from_type (type),
GRD_CREDENTIALS_FILE_KEY, &local_error);
if (!removed)
{
if (!g_error_matches (local_error,
G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND) &&
!g_error_matches (local_error,
G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
else
{
return TRUE;
}
}
return g_key_file_save_to_file (credentials_file->key_file,
credentials_file->filename, error);
}
GrdCredentialsFile *
grd_credentials_file_new (GError **error)
{
g_autoptr (GrdCredentialsFile) credentials_file = NULL;
g_autofree char *dir_path = NULL;
g_autoptr (GFile) dir = NULL;
credentials_file = g_object_new (GRD_TYPE_CREDENTIALS_FILE, NULL);
dir_path = g_build_path ("/",
g_get_user_data_dir (),
"gnome-remote-desktop",
NULL);
dir = g_file_new_for_path (dir_path);
if (!g_file_query_exists (dir, NULL))
{
if (!g_file_make_directory_with_parents (dir, NULL, error))
return NULL;
}
credentials_file->filename = g_build_path ("/",
dir_path,
"credentials.ini",
NULL);
if (!g_file_test (credentials_file->filename, G_FILE_TEST_IS_REGULAR))
if (!g_file_set_contents (credentials_file->filename, "", -1, error))
return NULL;
if (!g_key_file_load_from_file (credentials_file->key_file,
credentials_file->filename,
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
error))
return NULL;
return g_steal_pointer (&credentials_file);
}
static void
grd_credentials_file_finalize (GObject *object)
{
GrdCredentialsFile *credentials_file = GRD_CREDENTIALS_FILE (object);
g_clear_pointer (&credentials_file->filename, g_free);
g_clear_pointer (&credentials_file->key_file, g_key_file_unref);
G_OBJECT_CLASS (grd_credentials_file_parent_class)->finalize (object);
}
static void
grd_credentials_file_init (GrdCredentialsFile *credentials_file)
{
credentials_file->key_file = g_key_file_new ();
}
static void
grd_credentials_file_class_init (GrdCredentialsFileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdCredentialsClass *credentials_class = GRD_CREDENTIALS_CLASS (klass);
object_class->finalize = grd_credentials_file_finalize;
credentials_class->store = grd_credentials_file_store;
credentials_class->lookup = grd_credentials_file_lookup;
credentials_class->clear = grd_credentials_file_clear;
}

31
grd-credentials-file.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 SUSE LLC
*
* 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.
*
* Written by:
* Alynx Zhou <alynx.zhou@gmail.com>
*/
#pragma once
#include "grd-credentials.h"
#define GRD_TYPE_CREDENTIALS_FILE (grd_credentials_file_get_type ())
G_DECLARE_FINAL_TYPE (GrdCredentialsFile, grd_credentials_file, GRD,
CREDENTIALS_FILE, GrdCredentials)
GrdCredentialsFile * grd_credentials_file_new (GError **error);

252
grd-credentials-libsecret.c Normal file
View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2018-2022 Red Hat Inc.
* Copyright (C) 2020 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-credentials-libsecret.h"
#include <gio/gio.h>
#include <libsecret/secret.h>
#include <stdio.h>
#define GRD_RDP_CREDENTIALS_SCHEMA (get_rdp_schema ())
#define GRD_VNC_PASSWORD_SCHEMA (get_vnc_schema ())
#define GRD_VNC_LEGACY_PASSWORD_SCHEMA (get_legacy_vnc_schema ())
struct _GrdCredentialsLibsecret
{
GrdCredentials parent;
};
G_DEFINE_TYPE (GrdCredentialsLibsecret,
grd_credentials_libsecret,
GRD_TYPE_CREDENTIALS)
static const SecretSchema *
get_rdp_schema (void)
{
static const SecretSchema grd_rdp_credentials_schema = {
.name = "org.gnome.RemoteDesktop.RdpCredentials",
.flags = SECRET_SCHEMA_NONE,
.attributes = {
{ "credentials", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "NULL", 0 },
},
};
return &grd_rdp_credentials_schema;
}
static const SecretSchema *
get_legacy_vnc_schema (void)
{
static const SecretSchema grd_vnc_password_schema = {
.name = "org.gnome.RemoteDesktop.VncPassword",
.flags = SECRET_SCHEMA_NONE,
.attributes = {
{ "password", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "NULL", 0 },
},
};
return &grd_vnc_password_schema;
}
static const SecretSchema *
get_vnc_schema (void)
{
static const SecretSchema grd_vnc_password_schema = {
.name = "org.gnome.RemoteDesktop.VncCredentials",
.flags = SECRET_SCHEMA_NONE,
.attributes = {
{ "password", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ "NULL", 0 },
},
};
return &grd_vnc_password_schema;
}
static const SecretSchema *
schema_from_type (GrdCredentialsType type)
{
switch (type)
{
case GRD_CREDENTIALS_TYPE_RDP:
return GRD_RDP_CREDENTIALS_SCHEMA;
case GRD_CREDENTIALS_TYPE_VNC:
return GRD_VNC_PASSWORD_SCHEMA;
}
g_assert_not_reached ();
}
static const char *
description_from_type (GrdCredentialsType type)
{
switch (type)
{
case GRD_CREDENTIALS_TYPE_RDP:
return "GNOME Remote Desktop RDP credentials";
case GRD_CREDENTIALS_TYPE_VNC:
return "GNOME Remote Desktop VNC password";
}
g_assert_not_reached ();
}
static gboolean
grd_credentials_libsecret_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error)
{
g_autofree char *serialized = NULL;
g_variant_ref_sink (variant);
serialized = g_variant_print (variant, TRUE);
g_variant_unref (variant);
return secret_password_store_sync (schema_from_type (type),
SECRET_COLLECTION_DEFAULT,
description_from_type (type),
serialized,
NULL, error,
NULL);
}
static GVariant *
grd_credentials_libsecret_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
g_autofree char *serialized = NULL;
serialized = secret_password_lookup_sync (schema_from_type (type),
NULL, error,
NULL);
if (!serialized)
return NULL;
return g_variant_parse (NULL, serialized, NULL, NULL, error);
}
static gboolean
grd_credentials_libsecret_clear (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
g_autoptr (GError) local_error = NULL;
secret_password_clear_sync (schema_from_type (type),
NULL, &local_error,
NULL);
if (local_error)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
else
{
return TRUE;
}
}
GrdCredentialsLibsecret *
grd_credentials_libsecret_new (void)
{
return g_object_new (GRD_TYPE_CREDENTIALS_LIBSECRET, NULL);
}
static void
grd_credentials_libsecret_init (GrdCredentialsLibsecret *credentials_libsecret)
{
}
static void
maybe_migrate_legacy_vnc_password (GrdCredentials *credentials)
{
g_autoptr (GError) error = NULL;
g_autofree char *password = NULL;
password = secret_password_lookup_sync (GRD_VNC_LEGACY_PASSWORD_SCHEMA,
NULL, &error,
NULL);
if (!password)
{
if (error)
{
g_printerr ("Failed to lookup legacy VNC password schema: %s\n",
error->message);
}
}
else
{
g_printerr ("Migrating VNC password to new schema... ");
if (!grd_credentials_store (credentials,
GRD_CREDENTIALS_TYPE_VNC,
g_variant_new_string (password),
&error))
{
g_printerr ("Failed to migrate VNC password to new schema: %s\n",
error->message);
}
else
{
if (!secret_password_clear_sync (GRD_VNC_LEGACY_PASSWORD_SCHEMA,
NULL, &error, NULL) &&
error)
{
g_printerr ("Failed to clear VNC password from old schema: %s\n",
error->message);
}
else
{
g_printerr ("OK\n");
}
}
}
}
static void
grd_credentials_libsecret_constructed (GObject *object)
{
GrdCredentials *credentials = GRD_CREDENTIALS (object);
maybe_migrate_legacy_vnc_password (credentials);
G_OBJECT_CLASS (grd_credentials_libsecret_parent_class)->constructed (object);
}
static void
grd_credentials_libsecret_class_init (GrdCredentialsLibsecretClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdCredentialsClass *credentials_class = GRD_CREDENTIALS_CLASS (klass);
object_class->constructed = grd_credentials_libsecret_constructed;
credentials_class->store = grd_credentials_libsecret_store;
credentials_class->lookup = grd_credentials_libsecret_lookup;
credentials_class->clear = grd_credentials_libsecret_clear;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2018-2022 Red Hat Inc.
* Copyright (C) 2020 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.
*
*/
#pragma once
#include "grd-credentials.h"
#define GRD_TYPE_CREDENTIALS_LIBSECRET (grd_credentials_libsecret_get_type ())
G_DECLARE_FINAL_TYPE (GrdCredentialsLibsecret, grd_credentials_libsecret,
GRD, CREDENTIALS_LIBSECRET, GrdCredentials)
GrdCredentialsLibsecret * grd_credentials_libsecret_new (void);

232
grd-credentials-one-time.c Normal file
View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2022 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#include "config.h"
#include <fcntl.h>
#include <gio/gio.h>
#include "grd-credentials-one-time.h"
struct _GrdCredentialsOneTime
{
GrdCredentials parent;
struct
{
char *username;
char *password;
} rdp;
};
G_DEFINE_TYPE (GrdCredentialsOneTime,
grd_credentials_one_time,
GRD_TYPE_CREDENTIALS)
static char *
grd_generate_random_bytes (size_t size,
GError **error)
{
int fd;
int ret;
char *bytes;
errno = 0;
fd = open ("/dev/urandom", O_RDONLY);
if (fd < 0)
{
g_set_error_literal (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
g_strerror (errno));
return NULL;
}
bytes = g_malloc (size);
do
ret = read (fd, bytes, size);
while ((ret == -1 &&
(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) ||
ret < size);
close (fd);
return bytes;
}
static char *
generate_random_utf8_bytes (size_t len,
GError **error)
{
char *random_bytes;
int i;
random_bytes = grd_generate_random_bytes (len + 1, error);
if (!random_bytes)
return NULL;
/* UTF-8 chars defined with 1 byte always have the MSB to 0.
* Do not use ASCII control characters (0 - 32, 127). */
for (i = 0; i < len; ++i)
{
random_bytes[i] &= 127;
random_bytes[i] = (random_bytes[i] % 94) + 33;
}
random_bytes[len] = '\0';
return random_bytes;
}
static char *
generate_random_username (size_t len,
GError **error)
{
char *username;
username = generate_random_utf8_bytes (len, error);
if (!username)
return NULL;
/* The use of # at the beggining or : at any position,
* makes an error when looking up the user on the SAM file. */
return g_strdelimit (username, "#:", '_');
}
static gboolean
grd_credentials_one_time_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error)
{
GrdCredentialsOneTime *credentials_one_time =
GRD_CREDENTIALS_ONE_TIME (credentials);
g_autofree char *username = NULL;
g_autofree char *password = NULL;
g_assert (type == GRD_CREDENTIALS_TYPE_RDP);
g_variant_lookup (variant, "username", "s", &username);
if (!username)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Username not set");
return FALSE;
}
g_variant_lookup (variant, "password", "s", &password);
if (!password)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Password not set");
return FALSE;
}
g_clear_pointer (&credentials_one_time->rdp.username, g_free);
g_clear_pointer (&credentials_one_time->rdp.password, g_free);
credentials_one_time->rdp.username = g_steal_pointer (&username);
credentials_one_time->rdp.password = g_steal_pointer (&password);
return TRUE;
}
static GVariant *
grd_credentials_one_time_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
GrdCredentialsOneTime *credentials_one_time =
GRD_CREDENTIALS_ONE_TIME (credentials);
const char *rdp_username = credentials_one_time->rdp.username;
const char *rdp_password = credentials_one_time->rdp.password;
GVariantBuilder builder;
switch (type)
{
case GRD_CREDENTIALS_TYPE_RDP:
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}",
"username", g_variant_new_string (rdp_username));
g_variant_builder_add (&builder, "{sv}",
"password", g_variant_new_string (rdp_password));
return g_variant_builder_end (&builder);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Credentials type not found");
return NULL;
}
}
GrdCredentialsOneTime *
grd_credentials_one_time_new (void)
{
g_autoptr (GrdCredentialsOneTime) credentials_one_time = NULL;
g_autoptr (GError) error = NULL;
credentials_one_time = g_object_new (GRD_TYPE_CREDENTIALS_ONE_TIME, NULL);
credentials_one_time->rdp.username = generate_random_username (16, &error);
if (!credentials_one_time->rdp.username)
{
g_warning ("Failed to generate one time RDP username: %s", error->message);
return NULL;
}
credentials_one_time->rdp.password = generate_random_utf8_bytes (16, &error);
if (!credentials_one_time->rdp.password)
{
g_warning ("Failed to generate one time RDP password: %s", error->message);
return NULL;
}
return g_steal_pointer (&credentials_one_time);
}
static void
grd_credentials_one_time_finalize (GObject *object)
{
GrdCredentialsOneTime *credentials_one_time = GRD_CREDENTIALS_ONE_TIME (object);
g_clear_pointer (&credentials_one_time->rdp.username, g_free);
g_clear_pointer (&credentials_one_time->rdp.password, g_free);
G_OBJECT_CLASS (grd_credentials_one_time_parent_class)->finalize (object);
}
static void
grd_credentials_one_time_init (GrdCredentialsOneTime *credentials_one_time)
{
}
static void
grd_credentials_one_time_class_init (GrdCredentialsOneTimeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdCredentialsClass *credentials_class = GRD_CREDENTIALS_CLASS (klass);
object_class->finalize = grd_credentials_one_time_finalize;
credentials_class->store = grd_credentials_one_time_store;
credentials_class->lookup = grd_credentials_one_time_lookup;
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#pragma once
#include "grd-credentials.h"
#define GRD_TYPE_CREDENTIALS_ONE_TIME (grd_credentials_one_time_get_type ())
G_DECLARE_FINAL_TYPE (GrdCredentialsOneTime, grd_credentials_one_time,
GRD, CREDENTIALS_ONE_TIME, GrdCredentials)
GrdCredentialsOneTime *grd_credentials_one_time_new (void);

243
grd-credentials-tpm.c Normal file
View File

@@ -0,0 +1,243 @@
/*
* Copyright (C) 2022 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 "grd-credentials-tpm.h"
#include <gio/gio.h>
#include <stdio.h>
#include <string.h>
#include "grd-tpm.h"
struct _GrdCredentialsTpm
{
GrdCredentials parent;
};
G_DEFINE_TYPE (GrdCredentialsTpm, grd_credentials_tpm, GRD_TYPE_CREDENTIALS)
static const char *
secret_file_name_from_type (GrdCredentialsType type)
{
switch (type)
{
case GRD_CREDENTIALS_TYPE_RDP:
return "rdp-credentials.priv";
case GRD_CREDENTIALS_TYPE_VNC:
return "vnc-credentials.priv";
}
g_assert_not_reached ();
}
static gboolean
grd_credentials_tpm_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error)
{
g_autoptr (GrdTpm) tpm = NULL;
g_autofree const char *serialized = NULL;
ESYS_TR primary_handle = 0;
g_autofree TPMS_CONTEXT *primary = NULL;
g_autofree TPML_PCR_SELECTION *pcr_selection = NULL;
g_autofree TPML_DIGEST *pcr_digest = NULL;
g_autofree TPMS_CONTEXT *secret_tpms_context = NULL;
g_autoptr (GVariant) secret_variant = NULL;
g_autofree char *secret_serialized = NULL;
g_autofree char *dir_path = NULL;
g_autoptr (GFile) dir = NULL;
const char *secret_file_name;
g_autofree char *secret_path = NULL;
g_autoptr (GFile) secret_file = NULL;
g_variant_ref_sink (variant);
serialized = g_variant_print (variant, TRUE);
g_variant_unref (variant);
tpm = grd_tpm_new (GRD_TPM_MODE_WRITE, error);
if (!tpm)
return FALSE;
if (!grd_tpm_create_primary (tpm, &primary_handle, error))
return FALSE;
if (!grd_tpm_read_pcr (tpm, &pcr_selection, &pcr_digest, error))
return FALSE;
if (!grd_tpm_store_secret (tpm,
serialized,
primary_handle,
pcr_selection,
pcr_digest,
&secret_tpms_context,
error))
return FALSE;
secret_variant = grd_tpms_context_to_variant (secret_tpms_context);
dir_path = g_build_path ("/",
g_get_user_data_dir (),
"gnome-remote-desktop",
NULL);
dir = g_file_new_for_path (dir_path);
if (!g_file_query_exists (dir, NULL))
{
if (!g_file_make_directory_with_parents (dir, NULL, error))
return FALSE;
}
secret_file_name = secret_file_name_from_type (type);
secret_path = g_build_path ("/", dir_path, secret_file_name, NULL);
secret_file = g_file_new_for_path (secret_path);
secret_serialized = g_variant_print (secret_variant, TRUE);
return g_file_replace_contents (secret_file,
secret_serialized,
strlen (secret_serialized) + 1,
NULL, FALSE,
G_FILE_CREATE_PRIVATE |
G_FILE_CREATE_REPLACE_DESTINATION,
NULL, NULL, error);
}
static GVariant *
grd_credentials_tpm_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
g_autoptr (GrdTpm) tpm = NULL;
const char *secret_file_name;
g_autofree char *secret_path = NULL;
g_autofree char *serialized = NULL;
g_autofree TPML_PCR_SELECTION *pcr_selection = NULL;
g_autofree TPML_DIGEST *pcr_digest = NULL;
g_autoptr (GVariant) secret_variant = NULL;
g_autoptr (GError) local_error = NULL;
g_autofree char *credentials_string = NULL;
secret_file_name = secret_file_name_from_type (type);
secret_path = g_build_path ("/",
g_get_user_data_dir (),
"gnome-remote-desktop",
secret_file_name,
NULL);
if (!g_file_get_contents (secret_path,
&serialized,
NULL,
&local_error))
{
if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
return NULL;
g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
secret_variant = g_variant_parse (G_VARIANT_TYPE ("(uutqs)"), serialized,
NULL, NULL, error);
if (!secret_variant)
return NULL;
tpm = grd_tpm_new (GRD_TPM_MODE_READ, error);
if (!tpm)
return FALSE;
if (!grd_tpm_read_pcr (tpm, &pcr_selection, &pcr_digest, error))
return NULL;
credentials_string = grd_tpm_restore_secret (tpm,
secret_variant,
pcr_selection,
pcr_digest,
error);
if (!credentials_string)
return NULL;
return g_variant_parse (NULL, credentials_string, NULL, NULL, error);
}
static gboolean
grd_credentials_tpm_clear (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
const char *secret_file_name;
g_autofree char *secret_path = NULL;
g_autoptr (GFile) secret_file = NULL;
g_autoptr (GError) local_error = NULL;
secret_file_name = secret_file_name_from_type (type);
secret_path = g_build_path ("/",
g_get_user_data_dir (),
"gnome-remote-desktop",
secret_file_name,
NULL);
secret_file = g_file_new_for_path (secret_path);
if (!g_file_delete (secret_file, NULL, error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
return TRUE;
}
else
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
}
return TRUE;
}
GrdCredentialsTpm *
grd_credentials_tpm_new (GError **error)
{
g_autoptr (GrdTpm) tpm = NULL;
tpm = grd_tpm_new (GRD_TPM_MODE_NONE, error);
if (!tpm)
return NULL;
if (!grd_tpm_check_capabilities (tpm, error))
return NULL;
return g_object_new (GRD_TYPE_CREDENTIALS_TPM, NULL);
}
static void
grd_credentials_tpm_init (GrdCredentialsTpm *credentials_tpm)
{
}
static void
grd_credentials_tpm_class_init (GrdCredentialsTpmClass *klass)
{
GrdCredentialsClass *credentials_class = GRD_CREDENTIALS_CLASS (klass);
credentials_class->store = grd_credentials_tpm_store;
credentials_class->lookup = grd_credentials_tpm_lookup;
credentials_class->clear = grd_credentials_tpm_clear;
}

29
grd-credentials-tpm.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 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.
*
*/
#pragma once
#include "grd-credentials.h"
#define GRD_TYPE_CREDENTIALS_TPM (grd_credentials_tpm_get_type ())
G_DECLARE_FINAL_TYPE (GrdCredentialsTpm, grd_credentials_tpm,
GRD, CREDENTIALS_TPM, GrdCredentials)
GrdCredentialsTpm * grd_credentials_tpm_new (GError **error);

63
grd-credentials.c Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 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 "grd-credentials.h"
G_DEFINE_ABSTRACT_TYPE (GrdCredentials, grd_credentials, G_TYPE_OBJECT)
GVariant *
grd_credentials_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
return GRD_CREDENTIALS_GET_CLASS (credentials)->lookup (credentials,
type, error);
}
gboolean
grd_credentials_clear (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error)
{
return GRD_CREDENTIALS_GET_CLASS (credentials)->clear (credentials,
type, error);
}
gboolean
grd_credentials_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error)
{
return GRD_CREDENTIALS_GET_CLASS (credentials)->store (credentials,
type, variant, error);
}
static void
grd_credentials_init (GrdCredentials *credentials)
{
}
static void
grd_credentials_class_init (GrdCredentialsClass *klass)
{
}

64
grd-credentials.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 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.
*
*/
#pragma once
#include <glib-object.h>
typedef enum _GrdCredentialsType
{
GRD_CREDENTIALS_TYPE_RDP,
GRD_CREDENTIALS_TYPE_VNC,
} GrdCredentialsType;
#define GRD_TYPE_CREDENTIALS (grd_credentials_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdCredentials, grd_credentials,
GRD, CREDENTIALS, GObject)
struct _GrdCredentialsClass
{
GObjectClass parent_class;
gboolean (* store) (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error);
GVariant * (* lookup) (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error);
gboolean (* clear) (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error);
};
gboolean grd_credentials_store (GrdCredentials *credentials,
GrdCredentialsType type,
GVariant *variant,
GError **error);
GVariant * grd_credentials_lookup (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error);
gboolean grd_credentials_clear (GrdCredentials *credentials,
GrdCredentialsType type,
GError **error);

1199
grd-ctl.c Normal file

File diff suppressed because it is too large Load Diff

178
grd-cuda-avc-utils.cu Normal file
View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2021 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.
*/
/*
* Generate the PTX instructions with:
* clang --cuda-gpu-arch=sm_30 -S src/grd-cuda-avc-utils.cu -o data/grd-cuda-avc-utils_30.ptx --no-cuda-version-check -O3 --cuda-device-only -Wall -Wextra
*
* or
*
* nvcc -arch=compute_30 -ptx grd-cuda-avc-utils.cu -o grd-cuda-avc-utils_30.ptx
*
* Note: This requires CUDA < 11, since the generation of Kepler capable
* PTX code was removed from CUDA 11.
*/
#include <stdint.h>
extern "C"
{
__device__ uint8_t
rgb_to_y (uint8_t r,
uint8_t g,
uint8_t b)
{
return (54 * r + 183 * g + 18 * b) >> 8;
}
__device__ uint8_t
rgb_to_u (uint8_t r,
uint8_t g,
uint8_t b)
{
return ((-29 * r - 99 * g + 128 * b) >> 8) + 128;
}
__device__ uint8_t
rgb_to_v (uint8_t r,
uint8_t g,
uint8_t b)
{
return ((128 * r - 116 * g - 12 * b) >> 8) + 128;
}
__global__ void
convert_2x2_bgrx_area_to_yuv420_nv12 (uint8_t *dst_data,
uint32_t *src_data,
uint16_t src_width,
uint16_t src_height,
uint16_t aligned_width,
uint16_t aligned_height,
uint16_t aligned_stride)
{
uint8_t *dst_y0, *dst_y1, *dst_y2, *dst_y3, *dst_u, *dst_v;
uint32_t *src_u32;
uint16_t s0, s1, s2, s3;
uint32_t bgrx;
int32_t r_a, g_a, b_a;
uint8_t r, g, b;
uint16_t x_1x1, y_1x1;
uint16_t x_2x2, y_2x2;
x_2x2 = blockIdx.x * blockDim.x + threadIdx.x;
y_2x2 = blockIdx.y * blockDim.y + threadIdx.y;
if (x_2x2 >= aligned_width >> 1 || y_2x2 >= aligned_height >> 1)
return;
/*
* -------------
* | d_0 | d_1 |
* -------------
* | d_2 | d_3 |
* -------------
*/
s0 = 0;
s1 = 1;
s2 = src_width;
s3 = src_width + 1;
x_1x1 = x_2x2 << 1;
y_1x1 = y_2x2 << 1;
src_u32 = src_data + y_1x1 * src_width + x_1x1;
dst_y0 = dst_data + y_1x1 * aligned_stride + x_1x1;
dst_y1 = dst_y0 + 1;
dst_y2 = dst_data + (y_1x1 + 1) * aligned_stride + x_1x1;
dst_y3 = dst_y2 + 1;
dst_u = dst_data + aligned_height * aligned_stride +
y_2x2 * aligned_stride + x_1x1;
dst_v = dst_u + 1;
/* d_0 */
if (x_1x1 < src_width && y_1x1 < src_height)
{
bgrx = src_u32[s0];
b_a = b = *(((uint8_t *) &bgrx) + 0);
g_a = g = *(((uint8_t *) &bgrx) + 1);
r_a = r = *(((uint8_t *) &bgrx) + 2);
*dst_y0 = rgb_to_y (r, g, b);
}
else
{
b_a = b = 0;
g_a = g = 0;
r_a = r = 0;
*dst_y0 = 0;
}
if (x_1x1 + 1 < src_width && y_1x1 < src_height)
{
bgrx = src_u32[s1];
/* d_1 */
b_a += b = *(((uint8_t *) &bgrx) + 0);
g_a += g = *(((uint8_t *) &bgrx) + 1);
r_a += r = *(((uint8_t *) &bgrx) + 2);
*dst_y1 = rgb_to_y (r, g, b);
}
else
{
*dst_y1 = 0;
}
if (x_1x1 < src_width && y_1x1 + 1 < src_height)
{
bgrx = src_u32[s2];
/* d_2 */
b_a += b = *(((uint8_t *) &bgrx) + 0);
g_a += g = *(((uint8_t *) &bgrx) + 1);
r_a += r = *(((uint8_t *) &bgrx) + 2);
*dst_y2 = rgb_to_y (r, g, b);
if (x_1x1 + 1 < src_width)
{
bgrx = src_u32[s3];
/* d_3 */
b_a += b = *(((uint8_t *) &bgrx) + 0);
g_a += g = *(((uint8_t *) &bgrx) + 1);
r_a += r = *(((uint8_t *) &bgrx) + 2);
*dst_y3 = rgb_to_y (r, g, b);
}
else
{
*dst_y3 = 0;
}
}
else
{
*dst_y2 = 0;
*dst_y3 = 0;
}
b_a >>= 2;
g_a >>= 2;
r_a >>= 2;
*dst_u = rgb_to_u (r_a, g_a, b_a);
*dst_v = rgb_to_v (r_a, g_a, b_a);
}
}

138
grd-cuda-damage-utils.cu Normal file
View File

@@ -0,0 +1,138 @@
/*
* Copyright (C) 2021 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 <stdint.h>
extern "C"
{
__global__ void
check_damaged_pixel (uint8_t *damage_array,
uint8_t *region_is_damaged,
uint32_t *current_data,
uint32_t *previous_data,
uint32_t damage_array_stride,
uint32_t data_width,
uint32_t data_height,
uint32_t data_stride)
{
uint32_t data_pos;
uint8_t damaged = 0;
uint32_t x, y;
x = blockIdx.x * blockDim.x + threadIdx.x;
y = blockIdx.y * blockDim.y + threadIdx.y;
if (x >= data_width || y >= data_height)
return;
data_pos = y * data_stride + x;
if (previous_data[data_pos] != current_data[data_pos])
{
damaged = 1;
*region_is_damaged = 1;
}
damage_array[y * damage_array_stride + x] = damaged;
}
__global__ void
combine_damage_array_cols (uint8_t *damage_array,
uint32_t damage_array_width,
uint32_t damage_array_height,
uint32_t damage_array_stride,
uint32_t combine_shift)
{
uint32_t data_pos;
uint32_t neighbour_offset;
uint32_t x, y;
uint32_t sx;
sx = blockIdx.x * blockDim.x + threadIdx.x;
y = blockIdx.y * blockDim.y + threadIdx.y;
x = sx << combine_shift + 1;
if (x >= damage_array_width || y >= damage_array_height)
return;
neighbour_offset = 1 << combine_shift;
if (x + neighbour_offset >= damage_array_width)
return;
data_pos = y * damage_array_stride + x;
if (damage_array[data_pos + neighbour_offset])
damage_array[data_pos] = 1;
}
__global__ void
combine_damage_array_rows (uint8_t *damage_array,
uint32_t damage_array_width,
uint32_t damage_array_height,
uint32_t damage_array_stride,
uint32_t combine_shift)
{
uint32_t data_pos;
uint32_t neighbour_offset;
uint32_t x, y;
uint32_t sy;
x = blockIdx.x * blockDim.x + threadIdx.x;
sy = blockIdx.y * blockDim.y + threadIdx.y;
y = sy << combine_shift + 1;
if (x >= damage_array_width || y >= damage_array_height)
return;
neighbour_offset = 1 << combine_shift;
if (y + neighbour_offset >= damage_array_height)
return;
data_pos = y * damage_array_stride + x;
if (damage_array[data_pos + neighbour_offset * damage_array_stride])
damage_array[data_pos] = 1;
}
__global__ void
simplify_damage_array (uint8_t *dst_damage_array,
uint8_t *src_damage_array,
uint32_t dst_damage_array_stride,
uint32_t src_damage_array_width,
uint32_t src_damage_array_height,
uint32_t src_damage_array_stride)
{
uint32_t src_data_pos, dst_data_pos;
uint32_t sx, sy;
uint32_t x, y;
sx = blockIdx.x * blockDim.x + threadIdx.x;
sy = blockIdx.y * blockDim.y + threadIdx.y;
x = sx << 6;
y = sy << 6;
if (x >= src_damage_array_width || y >= src_damage_array_height)
return;
src_data_pos = y * src_damage_array_stride + x;
dst_data_pos = sy * dst_damage_array_stride + sx;
dst_damage_array[dst_data_pos] = src_damage_array[src_data_pos];
}
}

911
grd-daemon-handover.c Normal file
View File

@@ -0,0 +1,911 @@
/*
* Copyright (C) 2023 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#include "config.h"
#include "grd-daemon-handover.h"
#include <gio/gunixfdlist.h>
#include <glib/gi18n.h>
#include <unistd.h>
#include "grd-context.h"
#include "grd-daemon.h"
#include "grd-daemon-utils.h"
#include "grd-dbus-remote-desktop.h"
#include "grd-private.h"
#include "grd-prompt.h"
#include "grd-rdp-server.h"
#include "grd-session-rdp.h"
#include "grd-settings.h"
#include "grd-shell-dialog.h"
struct _GrdDaemonHandover
{
GrdDaemon parent;
GrdDBusRemoteDesktopRdpDispatcher *remote_desktop_dispatcher;
GrdDBusRemoteDesktopRdpHandover *remote_desktop_handover;
GDBusObjectManager *handover_object_manager;
GrdSession *session;
gboolean use_system_credentials;
GrdPrompt *prompt;
GCancellable *prompt_cancellable;
GrdShellDialog *dialog;
unsigned int gnome_remote_desktop_watch_name_id;
};
G_DEFINE_TYPE (GrdDaemonHandover, grd_daemon_handover, GRD_TYPE_DAEMON)
static void
on_remote_desktop_rdp_dispatcher_handover_requested (GObject *object,
GAsyncResult *result,
gpointer user_data);
static gboolean
grd_daemon_handover_is_ready (GrdDaemon *daemon)
{
GrdContext *context = grd_daemon_get_context (daemon);
if (!grd_context_get_mutter_remote_desktop_proxy (context) ||
!grd_context_get_mutter_screen_cast_proxy (context) ||
!GRD_DAEMON_HANDOVER (daemon)->remote_desktop_handover)
return FALSE;
return TRUE;
}
static void
on_take_client_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
int fd;
int fd_idx = -1;
g_autoptr (GError) error = NULL;
g_autoptr (GSocket) socket = NULL;
g_autoptr (GVariant) fd_variant = NULL;
g_autoptr (GUnixFDList) fd_list = NULL;
GrdDBusRemoteDesktopRdpHandover *proxy;
GrdDaemonHandover *daemon_handover;
GrdRdpServer *rdp_server;
GSocketConnection *socket_connection;
proxy = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
if (!grd_dbus_remote_desktop_rdp_handover_call_take_client_finish (
proxy, &fd_variant, &fd_list, result, &error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("[DaemonHandover] An error occurred while calling "
"TakeClient: %s", error->message);
return;
}
g_variant_get (fd_variant, "h", &fd_idx);
if (!G_IS_UNIX_FD_LIST (fd_list) ||
fd_idx < 0 || fd_idx >= g_unix_fd_list_get_length (fd_list))
{
g_warning ("Failed to acquire file descriptor: The fd list or fd index "
"is invalid");
return;
}
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
if (fd == -1)
{
g_warning ("[DaemonHandover] Failed to acquire file descriptor: %s",
error->message);
return;
}
socket = g_socket_new_from_fd (fd, &error);
if (!socket)
{
g_warning ("[DaemonHandover] Failed to create socket from fd: %s",
error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
rdp_server = grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));
socket_connection = g_socket_connection_factory_create_connection (socket);
grd_rdp_server_notify_incoming (G_SOCKET_SERVICE (rdp_server),
socket_connection);
}
static void
on_get_system_credentials_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusRemoteDesktopRdpHandover *remote_desktop_handover;
g_autoptr (GError) error = NULL;
g_autofree char *username = NULL;
g_autofree char *password = NULL;
GrdDaemonHandover *daemon_handover;
GrdContext *context;
GrdSettings *settings;
GCancellable *cancellable;
const char* object_path;
remote_desktop_handover = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
if (!grd_dbus_remote_desktop_rdp_handover_call_get_system_credentials_finish (
remote_desktop_handover,
&username,
&password,
result,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("[DaemonHandover] Failed to get system credentials: %s",
error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
settings = grd_context_get_settings (context);
if (!grd_settings_set_rdp_credentials (settings, username, password, &error))
{
g_warning ("[DaemonHanodver] Failed to overwrite credentials: %s",
error->message);
return;
}
object_path =
g_dbus_proxy_get_object_path (G_DBUS_PROXY (remote_desktop_handover));
g_debug ("[DaemonHandover] At: %s, calling TakeClient", object_path);
cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
grd_dbus_remote_desktop_rdp_handover_call_take_client (
remote_desktop_handover,
NULL,
cancellable,
on_take_client_finished,
daemon_handover);
}
static void
on_take_client_ready (GrdDBusRemoteDesktopRdpHandover *interface,
gboolean use_system_credentials,
GrdDaemonHandover *daemon_handover)
{
GCancellable *cancellable =
grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
const char* object_path;
object_path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (interface));
g_debug ("[DaemonHandover] At: %s, received TakeClientReady signal",
object_path);
daemon_handover->use_system_credentials = use_system_credentials;
if (use_system_credentials)
{
g_debug ("[DaemonHandover] At: %s, calling GetSystemCredentials",
object_path);
grd_dbus_remote_desktop_rdp_handover_call_get_system_credentials (
interface,
cancellable,
on_get_system_credentials_finished,
daemon_handover);
return;
}
g_debug ("[DaemonHandover] At: %s, calling TakeClient", object_path);
grd_dbus_remote_desktop_rdp_handover_call_take_client (
interface,
NULL,
cancellable,
on_take_client_finished,
daemon_handover);
}
static void
on_start_handover_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusRemoteDesktopRdpHandover *proxy;
g_autofree char *certificate = NULL;
g_autofree char *key = NULL;
g_autoptr (GError) error = NULL;
GrdDaemonHandover *daemon_handover;
GrdContext *context;
GrdSettings *settings;
proxy = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
if (!grd_dbus_remote_desktop_rdp_handover_call_start_handover_finish (
proxy, &certificate, &key, result, &error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("[DaemonHandover] Failed to start handover: %s",
error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
settings = grd_context_get_settings (context);
g_object_set (G_OBJECT (settings),
"rdp-server-cert", certificate,
"rdp-server-key", key,
NULL);
}
static void
start_handover (GrdDaemonHandover *daemon_handover)
{
GrdDaemon *daemon = GRD_DAEMON (daemon_handover);
GrdContext *context = grd_daemon_get_context (daemon);
GrdSettings *settings = grd_context_get_settings (context);
GCancellable *cancellable = grd_daemon_get_cancellable (daemon);
g_autofree char *username = NULL;
g_autofree char *password = NULL;
const char *object_path;
if (!grd_settings_get_rdp_credentials (settings,
&username, &password,
NULL))
g_assert_not_reached ();
object_path = g_dbus_proxy_get_object_path (
G_DBUS_PROXY (daemon_handover->remote_desktop_handover));
g_debug ("[DaemonHandover] At: %s, calling StartHandover", object_path);
grd_dbus_remote_desktop_rdp_handover_call_start_handover (
daemon_handover->remote_desktop_handover,
username,
password,
cancellable,
on_start_handover_finished,
daemon_handover);
}
static void
on_session_stopped (GrdSession *session,
GrdDaemonHandover *daemon_handover)
{
if (grd_is_remote_login ())
grd_session_manager_call_logout_sync ();
daemon_handover->session = NULL;
}
static gboolean
show_insecure_connection_dialog (GrdDaemonHandover *daemon_handover)
{
GCancellable *cancellable =
grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
g_assert (!daemon_handover->dialog);
daemon_handover->dialog = grd_shell_dialog_new (cancellable);
if (!daemon_handover->dialog)
return FALSE;
g_signal_connect_swapped (daemon_handover->dialog, "cancelled",
G_CALLBACK (grd_session_stop),
daemon_handover->session);
grd_shell_dialog_open (daemon_handover->dialog,
_("Continue With Insecure Connection?"),
/* Translators: Don't translate “use redirection server name:i:1”.
* It's a menu option, and it's the same for all languages. */
_("This Remote Desktop connection is insecure. "
"To secure this connection, enable RDSTLS Security "
"in your client by saving the connection settings "
"in your client as an RDP file and set "
"“use redirection server name:i:1” in it."),
_("Disconnect"),
_("Continue"));
return TRUE;
}
static void
prompt_response_callback (GObject *source_object,
GAsyncResult *async_result,
gpointer user_data)
{
GrdDaemonHandover *daemon_handover;
g_autoptr (GError) error = NULL;
GrdPromptResponse response;
if (!grd_prompt_query_finish (GRD_PROMPT (source_object),
async_result,
&response,
&error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
g_warning ("Failed to query user about session: %s", error->message);
grd_session_stop (daemon_handover->session);
}
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
switch (response)
{
case GRD_PROMPT_RESPONSE_ACCEPT:
return;
case GRD_PROMPT_RESPONSE_CANCEL:
grd_session_stop (daemon_handover->session);
return;
}
g_assert_not_reached ();
}
static void
show_insecure_connection_prompt (GrdDaemonHandover *daemon_handover)
{
g_autoptr (GrdPromptDefinition) prompt_definition = NULL;
g_assert (!daemon_handover->prompt);
daemon_handover->prompt = g_object_new (GRD_TYPE_PROMPT, NULL);
daemon_handover->prompt_cancellable = g_cancellable_new ();
prompt_definition = g_new0 (GrdPromptDefinition, 1);
prompt_definition->summary =
g_strdup_printf (_("Continue With Insecure Connection?"));
prompt_definition->body =
/* Translators: Don't translate “use redirection server name:i:1”.
* It's a menu option, and it's the same for all languages. */
g_strdup_printf (_("This Remote Desktop connection is insecure. "
"To secure this connection, enable RDSTLS Security "
"in your client by saving the connection settings "
"in your client as an RDP file and set "
"“use redirection server name:i:1” in it."));
prompt_definition->cancel_label = g_strdup_printf (_("Disconnect"));
prompt_definition->accept_label = g_strdup_printf (_("Continue"));
grd_prompt_query_async (daemon_handover->prompt,
prompt_definition,
daemon_handover->prompt_cancellable,
prompt_response_callback,
daemon_handover);
}
static void
inform_about_insecure_connection (GrdDaemonHandover *daemon_handover)
{
gboolean could_display_dialog;
could_display_dialog = show_insecure_connection_dialog (daemon_handover);
if (!could_display_dialog)
show_insecure_connection_prompt (daemon_handover);
}
static void
on_redirect_client (GrdDBusRemoteDesktopRdpHandover *interface,
const char *routing_token,
const char *username,
const char *password,
GrdDaemonHandover *daemon_handover)
{
const char *object_path =
g_dbus_proxy_get_object_path (G_DBUS_PROXY (interface));
GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
GrdSettings *settings = grd_context_get_settings (context);
GrdSessionRdp *session_rdp = GRD_SESSION_RDP (daemon_handover->session);
g_autofree char *certificate = NULL;
g_debug ("[DaemonHandover] At: %s, received RedirectClient signal",
object_path);
g_object_get (G_OBJECT (settings),
"rdp-server-cert", &certificate,
NULL);
if (!grd_session_rdp_send_server_redirection (session_rdp, routing_token,
username, password,
certificate))
grd_session_stop (daemon_handover->session);
}
static void
on_handover_is_waiting_changed (GrdDBusRemoteDesktopRdpHandover *proxy,
GParamSpec *pspec,
GrdDaemonHandover *daemon_handover)
{
gboolean handover_is_waiting;
handover_is_waiting =
grd_dbus_remote_desktop_rdp_handover_get_handover_is_waiting (
daemon_handover->remote_desktop_handover);
if (!handover_is_waiting)
return;
start_handover (daemon_handover);
}
static void
on_incoming_new_connection (GrdRdpServer *rdp_server,
GrdSession *session,
GrdDaemonHandover *daemon_handover)
{
GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
GrdSettings *settings = grd_context_get_settings (context);
if (daemon_handover->session)
{
g_signal_handlers_disconnect_by_func (daemon_handover->session,
G_CALLBACK (on_session_stopped),
daemon_handover);
grd_session_stop (daemon_handover->session);
}
g_signal_connect (session, "stopped",
G_CALLBACK (on_session_stopped),
daemon_handover);
daemon_handover->session = session;
grd_settings_recreate_rdp_credentials (settings);
if (daemon_handover->use_system_credentials && grd_is_remote_login ())
inform_about_insecure_connection (daemon_handover);
}
static void
setup_handover (GrdDaemonHandover *daemon_handover)
{
GrdRdpServer *rdp_server;
gboolean handover_is_waiting;
if (!daemon_handover->remote_desktop_handover)
return;
rdp_server = grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));
if (!rdp_server)
return;
g_signal_connect (daemon_handover->remote_desktop_handover, "take-client-ready",
G_CALLBACK (on_take_client_ready), daemon_handover);
g_signal_connect (daemon_handover->remote_desktop_handover, "redirect-client",
G_CALLBACK (on_redirect_client), daemon_handover);
g_signal_connect (daemon_handover->remote_desktop_handover, "notify::handover-is-waiting",
G_CALLBACK (on_handover_is_waiting_changed), daemon_handover);
g_signal_connect (rdp_server, "incoming-new-connection",
G_CALLBACK (on_incoming_new_connection), daemon_handover);
handover_is_waiting = grd_dbus_remote_desktop_rdp_handover_get_handover_is_waiting (
daemon_handover->remote_desktop_handover);
if (handover_is_waiting)
start_handover (daemon_handover);
}
static void
teardown_handover (GrdDaemonHandover *daemon_handover)
{
GrdRdpServer *rdp_server =
grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));
if (daemon_handover->remote_desktop_handover)
{
g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
G_CALLBACK (on_take_client_ready),
daemon_handover);
g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
G_CALLBACK (on_redirect_client),
daemon_handover);
g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
G_CALLBACK (on_handover_is_waiting_changed),
daemon_handover);
}
if (rdp_server)
{
g_signal_handlers_disconnect_by_func (rdp_server,
G_CALLBACK (on_incoming_new_connection),
daemon_handover);
}
}
static void
on_rdp_server_started (GrdDaemonHandover *daemon_handover)
{
setup_handover (daemon_handover);
}
static void
on_rdp_server_stopped (GrdDaemonHandover *daemon_handover)
{
teardown_handover (daemon_handover);
}
static void
on_handover_object_added (GDBusObjectManager *manager,
GDBusObject *object,
GrdDaemonHandover *daemon_handover)
{
g_autofree char *session_id = NULL;
g_autofree char *expected_object_path = NULL;
const char *object_path;
GCancellable *cancellable;
session_id = grd_get_session_id_from_pid (getpid ());
if (!session_id)
{
g_warning ("[DaemonHandover] Could not get session id");
return;
}
expected_object_path = g_strdup_printf ("%s/session%s",
REMOTE_DESKTOP_HANDOVERS_OBJECT_PATH,
session_id);
object_path = g_dbus_object_get_object_path (object);
if (g_strcmp0 (object_path, expected_object_path) != 0)
{
g_debug ("[DaemonHandover] Ignoring handover object at: %s, "
"expected: %s", object_path, expected_object_path);
return;
}
cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover (
daemon_handover->remote_desktop_dispatcher,
cancellable,
on_remote_desktop_rdp_dispatcher_handover_requested,
daemon_handover);
}
static void
on_handover_object_manager_acquired (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr (GDBusObjectManager) manager = NULL;
g_autoptr (GError) error = NULL;
GrdDaemonHandover *daemon_handover;
manager =
grd_dbus_remote_desktop_object_manager_client_new_for_bus_finish (result,
&error);
if (!manager)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("[DaemonHandover] Failed to create handover object manager: %s",
error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
daemon_handover->handover_object_manager = g_steal_pointer (&manager);
g_debug ("[DaemonHandover] Watching handover objects");
g_signal_connect (daemon_handover->handover_object_manager, "object-added",
G_CALLBACK (on_handover_object_added), daemon_handover);
}
static void
start_watching_handover_objects (GrdDaemonHandover *daemon_handover)
{
GCancellable *cancellable;
if (daemon_handover->handover_object_manager)
return;
cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
grd_dbus_remote_desktop_object_manager_client_new_for_bus (
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
REMOTE_DESKTOP_BUS_NAME,
REMOTE_DESKTOP_HANDOVERS_OBJECT_PATH,
cancellable,
on_handover_object_manager_acquired,
daemon_handover);
}
static void
stop_watching_handover_objects (GrdDaemonHandover *daemon_handover)
{
if (!daemon_handover->handover_object_manager)
return;
g_signal_handlers_disconnect_by_func (daemon_handover->handover_object_manager,
G_CALLBACK (on_handover_object_added),
daemon_handover);
g_clear_object (&daemon_handover->handover_object_manager);
}
static void
on_remote_desktop_rdp_handover_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDaemonHandover *daemon_handover;
g_autoptr (GrdDBusRemoteDesktopRdpHandover) proxy = NULL;
g_autoptr (GError) error = NULL;
proxy =
grd_dbus_remote_desktop_rdp_handover_proxy_new_for_bus_finish (result,
&error);
if (!proxy)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("[DaemonHandover] Failed to create remote desktop handover "
"proxy: %s", error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
stop_watching_handover_objects (daemon_handover);
daemon_handover->remote_desktop_handover = g_steal_pointer (&proxy);
setup_handover (daemon_handover);
grd_daemon_maybe_enable_services (GRD_DAEMON (daemon_handover));
}
static void
on_remote_desktop_rdp_dispatcher_handover_requested (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusRemoteDesktopRdpDispatcher *remote_desktop_dispatcher;
g_autofree char *object_path = NULL;
g_autoptr (GError) error = NULL;
GrdDaemonHandover *daemon_handover;
GCancellable *cancellable;
gboolean success;
remote_desktop_dispatcher = GRD_DBUS_REMOTE_DESKTOP_RDP_DISPATCHER (object);
success =
grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover_finish (
remote_desktop_dispatcher,
&object_path,
result,
&error);
if (!success)
{
if (g_error_matches (error, GRD_DBUS_ERROR, GRD_DBUS_ERROR_NO_HANDOVER))
{
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
start_watching_handover_objects (daemon_handover);
}
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_warning ("[DaemonHandover] Failed to request remote desktop "
"handover: %s", error->message);
}
return;
}
g_debug ("[DaemonHandover] Using: %s, from dispatcher request",
object_path);
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
grd_dbus_remote_desktop_rdp_handover_proxy_new_for_bus (
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
REMOTE_DESKTOP_BUS_NAME,
object_path,
cancellable,
on_remote_desktop_rdp_handover_proxy_acquired,
daemon_handover);
}
static void
on_remote_desktop_rdp_dispatcher_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr (GrdDBusRemoteDesktopRdpDispatcher) proxy = NULL;
g_autoptr (GError) error = NULL;
GrdDaemonHandover *daemon_handover;
GCancellable *cancellable;
proxy =
grd_dbus_remote_desktop_rdp_dispatcher_proxy_new_finish (result, &error);
if (!proxy)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("[DaemonHandover] Failed to create remote desktop "
"dispatcher proxy: %s", error->message);
return;
}
daemon_handover = GRD_DAEMON_HANDOVER (user_data);
g_assert (!daemon_handover->remote_desktop_dispatcher);
daemon_handover->remote_desktop_dispatcher = g_steal_pointer (&proxy);
cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover (
daemon_handover->remote_desktop_dispatcher,
cancellable,
on_remote_desktop_rdp_dispatcher_handover_requested,
daemon_handover);
}
static void
on_gnome_remote_desktop_name_appeared (GDBusConnection *connection,
const char *name,
const char *name_owner,
gpointer user_data)
{
GrdDaemonHandover *daemon_handover = user_data;
GCancellable *cancellable =
grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
g_debug ("[DaemonHandover] %s name appeared", name);
grd_dbus_remote_desktop_rdp_dispatcher_proxy_new (
connection,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
REMOTE_DESKTOP_BUS_NAME,
REMOTE_DESKTOP_DISPATCHER_OBJECT_PATH,
cancellable,
on_remote_desktop_rdp_dispatcher_proxy_acquired,
daemon_handover);
}
static void
on_gnome_remote_desktop_name_vanished (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
GrdDaemonHandover *daemon_handover = user_data;
g_warning ("[DaemonHandover] %s name vanished", name);
teardown_handover (daemon_handover);
g_clear_object (&daemon_handover->remote_desktop_handover);
g_clear_object (&daemon_handover->remote_desktop_dispatcher);
stop_watching_handover_objects (daemon_handover);
if (grd_is_remote_login ())
grd_session_manager_call_logout_sync ();
}
GrdDaemonHandover *
grd_daemon_handover_new (GError **error)
{
GrdContext *context;
context = grd_context_new (GRD_RUNTIME_MODE_HANDOVER, error);
if (!context)
return NULL;
return g_object_new (GRD_TYPE_DAEMON_HANDOVER,
"application-id", GRD_DAEMON_HANDOVER_APPLICATION_ID,
"flags", G_APPLICATION_IS_SERVICE,
"context", context,
NULL);
}
static void
grd_daemon_handover_init (GrdDaemonHandover *daemon_handover)
{
}
static void
grd_daemon_handover_startup (GApplication *app)
{
GrdDaemonHandover *daemon_handover = GRD_DAEMON_HANDOVER (app);
grd_daemon_acquire_mutter_dbus_proxies (GRD_DAEMON (daemon_handover));
g_signal_connect (daemon_handover, "mutter-proxy-acquired",
G_CALLBACK (grd_daemon_maybe_enable_services), NULL);
daemon_handover->gnome_remote_desktop_watch_name_id =
g_bus_watch_name (G_BUS_TYPE_SYSTEM,
REMOTE_DESKTOP_BUS_NAME,
G_BUS_NAME_WATCHER_FLAGS_NONE,
on_gnome_remote_desktop_name_appeared,
on_gnome_remote_desktop_name_vanished,
daemon_handover, NULL);
g_signal_connect (daemon_handover, "rdp-server-started",
G_CALLBACK (on_rdp_server_started), NULL);
g_signal_connect (daemon_handover, "rdp-server-stopped",
G_CALLBACK (on_rdp_server_stopped), NULL);
G_APPLICATION_CLASS (grd_daemon_handover_parent_class)->startup (app);
}
static void
grd_daemon_handover_shutdown (GApplication *app)
{
GrdDaemonHandover *daemon_handover = GRD_DAEMON_HANDOVER (app);
GrdDaemon *daemon = GRD_DAEMON (daemon_handover);
g_cancellable_cancel (grd_daemon_get_cancellable (daemon));
g_clear_object (&daemon_handover->remote_desktop_handover);
g_clear_object (&daemon_handover->remote_desktop_dispatcher);
stop_watching_handover_objects (daemon_handover);
g_clear_handle_id (&daemon_handover->gnome_remote_desktop_watch_name_id,
g_bus_unwatch_name);
if (daemon_handover->prompt_cancellable)
{
g_cancellable_cancel (daemon_handover->prompt_cancellable);
g_clear_object (&daemon_handover->prompt_cancellable);
}
g_clear_object (&daemon_handover->prompt);
g_clear_object (&daemon_handover->dialog);
G_APPLICATION_CLASS (grd_daemon_handover_parent_class)->shutdown (app);
}
static void
grd_daemon_handover_class_init (GrdDaemonHandoverClass *klass)
{
GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
GrdDaemonClass *daemon_class = GRD_DAEMON_CLASS (klass);
g_application_class->startup = grd_daemon_handover_startup;
g_application_class->shutdown = grd_daemon_handover_shutdown;
daemon_class->is_daemon_ready = grd_daemon_handover_is_ready;
}

31
grd-daemon-handover.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#pragma once
#include "grd-daemon.h"
#define GRD_TYPE_DAEMON_HANDOVER (grd_daemon_handover_get_type ())
G_DECLARE_FINAL_TYPE (GrdDaemonHandover, grd_daemon_handover,
GRD, DAEMON_HANDOVER, GrdDaemon)
GrdDaemonHandover *grd_daemon_handover_new (GError **error);

1421
grd-daemon-system.c Normal file

File diff suppressed because it is too large Load Diff

31
grd-daemon-system.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 SUSE Software Solutions Germany GmbH
*
* 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.
*
* Written by:
* Joan Torres <joan.torres@suse.com>
*/
#pragma once
#include "grd-daemon.h"
#define GRD_TYPE_DAEMON_SYSTEM (grd_daemon_system_get_type ())
G_DECLARE_FINAL_TYPE (GrdDaemonSystem, grd_daemon_system,
GRD, DAEMON_SYSTEM, GrdDaemon)
GrdDaemonSystem *grd_daemon_system_new (GError **error);

291
grd-daemon-user.c Normal file
View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2022 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#include "config.h"
#include "grd-daemon-user.h"
#include <glib/gi18n.h>
#include "grd-private.h"
#include "grd-prompt.h"
struct _GrdDaemonUser
{
GrdDaemon parent;
#ifdef HAVE_RDP
GrdDBusRemoteDesktopRdpServer *system_rdp_server;
GrdPrompt *prompt;
GCancellable *prompt_cancellable;
#endif /* HAVE_RDP */
};
G_DEFINE_TYPE (GrdDaemonUser, grd_daemon_user, GRD_TYPE_DAEMON)
#ifdef HAVE_RDP
static void
on_rdp_server_stopped (GrdDaemonUser *daemon_user)
{
if (daemon_user->prompt_cancellable)
{
g_cancellable_cancel (daemon_user->prompt_cancellable);
g_clear_object (&daemon_user->prompt_cancellable);
}
g_clear_object (&daemon_user->prompt);
g_signal_handlers_disconnect_by_func (daemon_user,
G_CALLBACK (on_rdp_server_stopped),
NULL);
}
static void
prompt_response_callback (GObject *source_object,
GAsyncResult *async_result,
gpointer user_data)
{
GrdDaemonUser *daemon_user = GRD_DAEMON_USER (user_data);
g_autoptr (GError) error = NULL;
GrdPromptResponse response;
if (!grd_prompt_query_finish (daemon_user->prompt,
async_result,
&response,
&error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_warning ("Failed to query user about port change: %s",
error->message);
}
}
g_clear_object (&daemon_user->prompt);
g_clear_object (&daemon_user->prompt_cancellable);
}
static void
show_port_changed_prompt (GrdDaemonUser *daemon_user,
const int system_rdp_port,
const int selected_rdp_port)
{
g_autoptr (GrdPromptDefinition) prompt_definition = NULL;
daemon_user->prompt = g_object_new (GRD_TYPE_PROMPT, NULL);
daemon_user->prompt_cancellable = g_cancellable_new ();
prompt_definition = g_new0 (GrdPromptDefinition, 1);
prompt_definition->summary =
g_strdup_printf (_("Desktop Sharing port changed"));
prompt_definition->body =
g_strdup_printf (_("The Desktop Sharing port has changed from <b>%i</b> to "
"<b>%i</b>.\n"
"This is because the Remote Sessions service has been "
"activated and it has a higher priority to use port "
"<b>%i</b>."),
system_rdp_port, selected_rdp_port, system_rdp_port);
prompt_definition->accept_label = g_strdup_printf (_("Accept"));
grd_prompt_query_async (daemon_user->prompt,
prompt_definition,
daemon_user->prompt_cancellable,
prompt_response_callback,
daemon_user);
}
static void
on_rdp_server_started (GrdDaemonUser *daemon_user)
{
GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_user));
GrdDBusRemoteDesktopRdpServer *rdp_server_iface =
grd_context_get_rdp_server_interface (context);
int selected_rdp_port =
grd_dbus_remote_desktop_rdp_server_get_port (rdp_server_iface);
int system_rdp_port =
grd_dbus_remote_desktop_rdp_server_get_port (daemon_user->system_rdp_server);
g_assert (selected_rdp_port != system_rdp_port);
show_port_changed_prompt (daemon_user, system_rdp_port, selected_rdp_port);
g_signal_handlers_disconnect_by_func (daemon_user,
G_CALLBACK (on_rdp_server_started),
NULL);
}
static void
on_system_rdp_server_binding (GrdDBusRemoteDesktopRdpServer *system_rdp_server,
int system_rdp_port,
GrdDaemonUser *daemon_user)
{
GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_user));
GrdDBusRemoteDesktopRdpServer *user_rdp_server =
grd_context_get_rdp_server_interface (context);
int user_rdp_port =
grd_dbus_remote_desktop_rdp_server_get_port (user_rdp_server);
if (system_rdp_port != user_rdp_port)
return;
g_debug ("[DaemonUser] Restarting RDP server due to port conflict with the "
"system daemon");
g_signal_connect (daemon_user, "rdp-server-stopped",
G_CALLBACK (on_rdp_server_stopped), NULL);
g_signal_connect (daemon_user, "rdp-server-started",
G_CALLBACK (on_rdp_server_started), NULL);
grd_daemon_restart_rdp_server_with_delay (GRD_DAEMON (daemon_user));
}
static void
on_remote_desktop_rdp_server_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDaemonUser *daemon_user = user_data;
g_autoptr (GError) error = NULL;
daemon_user->system_rdp_server =
grd_dbus_remote_desktop_rdp_server_proxy_new_for_bus_finish (result, &error);
if (!daemon_user->system_rdp_server)
{
g_warning ("[DaemonUser] Failed to create system RDP server proxy: "
"%s", error->message);
return;
}
g_signal_connect (daemon_user->system_rdp_server, "binding",
G_CALLBACK (on_system_rdp_server_binding),
daemon_user);
}
#endif /* HAVE_RDP */
GrdDaemonUser *
grd_daemon_user_new (GrdRuntimeMode runtime_mode,
GError **error)
{
GrdContext *context;
GrdDaemonUser *daemon_user;
const char *application_id = NULL;
context = grd_context_new (runtime_mode, error);
if (!context)
return NULL;
switch (runtime_mode)
{
case GRD_RUNTIME_MODE_SCREEN_SHARE:
application_id = GRD_DAEMON_USER_APPLICATION_ID;
break;
case GRD_RUNTIME_MODE_HEADLESS:
application_id = GRD_DAEMON_HEADLESS_APPLICATION_ID;
break;
case GRD_RUNTIME_MODE_SYSTEM:
case GRD_RUNTIME_MODE_HANDOVER:
g_assert_not_reached ();
}
daemon_user = g_object_new (GRD_TYPE_DAEMON_USER,
"application-id", application_id,
"flags", G_APPLICATION_IS_SERVICE,
"context", context,
NULL);
return daemon_user;
}
static void
grd_daemon_user_init (GrdDaemonUser *daemon_user)
{
}
static void
grd_daemon_user_startup (GApplication *app)
{
GrdDaemonUser *daemon_user = GRD_DAEMON_USER (app);
#ifdef HAVE_RDP
GCancellable *cancellable =
grd_daemon_get_cancellable (GRD_DAEMON (daemon_user));
#endif /* HAVE_RDP */
grd_daemon_acquire_mutter_dbus_proxies (GRD_DAEMON (daemon_user));
g_signal_connect (daemon_user, "mutter-proxy-acquired",
G_CALLBACK (grd_daemon_maybe_enable_services), NULL);
#ifdef HAVE_RDP
grd_dbus_remote_desktop_rdp_server_proxy_new_for_bus (
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
REMOTE_DESKTOP_BUS_NAME,
GRD_RDP_SERVER_OBJECT_PATH,
cancellable,
on_remote_desktop_rdp_server_proxy_acquired,
daemon_user);
#endif /* HAVE_RDP */
G_APPLICATION_CLASS (grd_daemon_user_parent_class)->startup (app);
}
static void
grd_daemon_user_shutdown (GApplication *app)
{
#ifdef HAVE_RDP
GrdDaemonUser *daemon_user = GRD_DAEMON_USER (app);
g_clear_object (&daemon_user->system_rdp_server);
if (daemon_user->prompt_cancellable)
{
g_cancellable_cancel (daemon_user->prompt_cancellable);
g_clear_object (&daemon_user->prompt_cancellable);
}
g_clear_object (&daemon_user->prompt);
#endif /* HAVE_RDP */
G_APPLICATION_CLASS (grd_daemon_user_parent_class)->shutdown (app);
}
static gboolean
grd_daemon_user_is_ready (GrdDaemon *daemon)
{
GrdContext *context = grd_daemon_get_context (daemon);
if (!grd_context_get_mutter_remote_desktop_proxy (context) ||
!grd_context_get_mutter_screen_cast_proxy (context))
return FALSE;
return TRUE;
}
static void
grd_daemon_user_class_init (GrdDaemonUserClass *klass)
{
GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
GrdDaemonClass *daemon_class = GRD_DAEMON_CLASS (klass);
g_application_class->startup = grd_daemon_user_startup;
g_application_class->shutdown = grd_daemon_user_shutdown;
daemon_class->is_daemon_ready = grd_daemon_user_is_ready;
}

34
grd-daemon-user.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2022 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#pragma once
#include "grd-daemon.h"
#define GRD_TYPE_DAEMON_USER (grd_daemon_user_get_type ())
G_DECLARE_FINAL_TYPE (GrdDaemonUser,
grd_daemon_user,
GRD, DAEMON_USER,
GrdDaemon)
GrdDaemonUser *grd_daemon_user_new (GrdRuntimeMode runtime_mode,
GError **error);

232
grd-daemon-utils.c Normal file
View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2023 SUSE Software Solutions Germany GmbH
*
* 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-daemon-utils.h"
#include <systemd/sd-login.h>
enum
{
GSM_LOGOUT_MODE_NORMAL = 0,
GSM_LOGOUT_MODE_NO_CONFIRMATION,
GSM_LOGOUT_MODE_FORCE,
};
gboolean
grd_get_pid_of_sender_sync (GDBusConnection *connection,
const char *name,
pid_t *out_pid,
GCancellable *cancellable,
GError **error)
{
g_autoptr (GVariant) result = NULL;
uint32_t pid;
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_assert (out_pid);
result = g_dbus_connection_call_sync (connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetConnectionUnixProcessID",
g_variant_new ("(s)", name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable, error);
if (!result)
return FALSE;
g_variant_get (result, "(u)", &pid);
*out_pid = (pid_t) pid;
return TRUE;
}
gboolean
grd_get_uid_of_sender_sync (GDBusConnection *connection,
const char *name,
uid_t *out_uid,
GCancellable *cancellable,
GError **error)
{
g_autoptr (GVariant) result = NULL;
uint32_t uid;
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_assert (out_uid);
result = g_dbus_connection_call_sync (connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetConnectionUnixUser",
g_variant_new ("(s)", name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable, error);
if (!result)
return FALSE;
g_variant_get (result, "(u)", &uid);
*out_uid = (uid_t) uid;
return TRUE;
}
char *
grd_get_session_id_from_pid (pid_t pid)
{
char *session_id = NULL;
int res;
res = sd_pid_get_session (pid, &session_id);
if (res < 0 && res != -ENODATA)
{
g_warning ("Failed to retrieve session information for "
"pid %d: %s", (int) pid, strerror (-res));
}
return g_steal_pointer (&session_id);
}
static gboolean
grd_sd_session_is_graphical (const char *session_id)
{
const char * const graphical_session_types[] = { "wayland", "x11", NULL };
int res;
g_autofree char *type = NULL;
res = sd_session_get_type (session_id, &type);
if (res < 0)
return FALSE;
return g_strv_contains (graphical_session_types, type);
}
static gboolean
grd_sd_session_is_active (const char *session_id)
{
const char * const active_states[] = { "active", "online", NULL };
int res;
g_autofree char *state = NULL;
res = sd_session_get_state (session_id, &state);
if (res < 0)
return FALSE;
return g_strv_contains (active_states, state);
}
char *
grd_get_session_id_from_uid (uid_t uid)
{
g_auto (GStrv) sessions = NULL;
char *session_id = NULL;
int n_sessions;
int i;
n_sessions = sd_uid_get_sessions (uid, 0, &sessions);
for (i = n_sessions - 1; i >= 0; i--)
{
if (!grd_sd_session_is_graphical (sessions[i]))
continue;
if (!grd_sd_session_is_active (sessions[i]))
continue;
session_id = sessions[i];
break;
}
return g_strdup (session_id);
}
gboolean
grd_is_remote_login (void)
{
g_autofree char *session_id = NULL;
g_autofree char *class = NULL;
pid_t pid;
uid_t uid;
int ret;
pid = getpid ();
session_id = grd_get_session_id_from_pid (pid);
if (!session_id)
{
uid = getuid ();
session_id = grd_get_session_id_from_uid (uid);
}
if (!session_id)
{
g_warning ("Couldn't get session id");
return FALSE;
}
ret = sd_session_get_class (session_id, &class);
if (ret < 0)
{
g_warning ("Couldn't determine session class: %s", g_strerror (-ret));
return FALSE;
}
return g_strcmp0 (class, "greeter") == 0 &&
sd_session_is_remote (session_id) == 1;
}
void
grd_session_manager_call_logout_sync (void)
{
g_autoptr (GDBusConnection) connection = NULL;
g_autoptr (GError) error = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (!connection)
{
g_warning ("Couldn't get session bus to logout: %s", error->message);
return;
}
g_dbus_connection_call_sync (connection,
"org.gnome.SessionManager",
"/org/gnome/SessionManager",
"org.gnome.SessionManager",
"Logout",
g_variant_new ("(u)",
GSM_LOGOUT_MODE_NO_CONFIRMATION),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL, &error);
if (error)
g_warning ("Couldn't logout of session: %s", error->message);
}

42
grd-daemon-utils.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 SUSE Software Solutions Germany GmbH
*
* 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.
*/
#pragma once
#include <gio/gio.h>
gboolean grd_get_pid_of_sender_sync (GDBusConnection *connection,
const char *name,
pid_t *out_pid,
GCancellable *cancellable,
GError **error);
gboolean grd_get_uid_of_sender_sync (GDBusConnection *connection,
const char *name,
uid_t *out_uid,
GCancellable *cancellable,
GError **error);
char *grd_get_session_id_from_pid (pid_t pid);
char *grd_get_session_id_from_uid (uid_t uid);
gboolean grd_is_remote_login (void);
void grd_session_manager_call_logout_sync (void);

1248
grd-daemon.c Normal file

File diff suppressed because it is too large Load Diff

53
grd-daemon.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2015 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#pragma once
#include <gio/gio.h>
#include "grd-context.h"
typedef struct _GrdDaemon GrdDaemon;
#define GRD_TYPE_DAEMON (grd_daemon_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdDaemon, grd_daemon, GRD, DAEMON, GApplication)
struct _GrdDaemonClass
{
GApplicationClass parent_class;
gboolean (*is_daemon_ready) (GrdDaemon *daemon);
};
GCancellable *grd_daemon_get_cancellable (GrdDaemon *daemon);
GrdContext *grd_daemon_get_context (GrdDaemon *daemon);
GrdRdpServer *grd_daemon_get_rdp_server (GrdDaemon *daemon);
void grd_daemon_restart_rdp_server_with_delay (GrdDaemon *daemon);
void grd_daemon_maybe_enable_services (GrdDaemon *daemon);
void grd_daemon_disable_services (GrdDaemon *daemon);
void grd_daemon_acquire_mutter_dbus_proxies (GrdDaemon *daemon);

188
grd-damage-detector-sw.c Normal file
View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2025 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-damage-detector-sw.h"
#include "grd-damage-utils.h"
#include "grd-local-buffer.h"
#include "grd-utils.h"
#define TILE_WIDTH 64
#define TILE_HEIGHT 64
struct _GrdDamageDetectorSw
{
GObject parent;
uint32_t surface_width;
uint32_t surface_height;
uint32_t damage_buffer_width;
uint32_t damage_buffer_height;
uint32_t *damage_buffer;
uint32_t damage_buffer_length;
};
G_DEFINE_TYPE (GrdDamageDetectorSw, grd_damage_detector_sw,
G_TYPE_OBJECT)
uint32_t *
grd_damage_detector_sw_get_damage_buffer (GrdDamageDetectorSw *damage_detector)
{
return damage_detector->damage_buffer;
}
uint32_t
grd_damage_detector_sw_get_damage_buffer_length (GrdDamageDetectorSw *damage_detector)
{
return damage_detector->damage_buffer_length;
}
static void
invalidate_surface (GrdDamageDetectorSw *damage_detector)
{
uint32_t i;
for (i = 0; i < damage_detector->damage_buffer_length; ++i)
damage_detector->damage_buffer[i] = 1;
}
static void
compute_frame_damage (GrdDamageDetectorSw *damage_detector,
GrdLocalBuffer *local_buffer_new,
GrdLocalBuffer *local_buffer_old)
{
uint32_t surface_width = damage_detector->surface_width;
uint32_t surface_height = damage_detector->surface_height;
uint32_t local_buffer_stride;
uint32_t damage_buffer_stride;
uint32_t x, y;
g_assert (local_buffer_new);
g_assert (local_buffer_old);
g_assert (grd_local_buffer_get_buffer_stride (local_buffer_new) ==
grd_local_buffer_get_buffer_stride (local_buffer_old));
local_buffer_stride = grd_local_buffer_get_buffer_stride (local_buffer_new);
damage_buffer_stride = damage_detector->damage_buffer_width;
for (y = 0; y < damage_detector->damage_buffer_height; ++y)
{
for (x = 0; x < damage_detector->damage_buffer_width; ++x)
{
cairo_rectangle_int_t tile;
uint32_t tile_damaged = 0;
uint32_t buffer_pos;
tile.x = x * TILE_WIDTH;
tile.y = y * TILE_HEIGHT;
tile.width = surface_width - tile.x < TILE_WIDTH ?
surface_width - tile.x : TILE_WIDTH;
tile.height = surface_height - tile.y < TILE_HEIGHT ?
surface_height - tile.y : TILE_HEIGHT;
if (grd_is_tile_dirty (&tile,
grd_local_buffer_get_buffer (local_buffer_new),
grd_local_buffer_get_buffer (local_buffer_old),
local_buffer_stride, 4))
tile_damaged = 1;
buffer_pos = y * damage_buffer_stride + x;
damage_detector->damage_buffer[buffer_pos] = tile_damaged;
}
}
}
void
grd_damage_detector_sw_compute_damage (GrdDamageDetectorSw *damage_detector,
GrdLocalBuffer *local_buffer_new,
GrdLocalBuffer *local_buffer_old)
{
if (!local_buffer_old)
{
invalidate_surface (damage_detector);
return;
}
compute_frame_damage (damage_detector, local_buffer_new, local_buffer_old);
}
static void
create_damage_buffer (GrdDamageDetectorSw *damage_detector)
{
uint32_t surface_width = damage_detector->surface_width;
uint32_t surface_height = damage_detector->surface_height;
uint32_t damage_buffer_width;
uint32_t damage_buffer_height;
uint32_t damage_buffer_length;
damage_buffer_width = grd_get_aligned_size (surface_width, TILE_WIDTH) /
TILE_WIDTH;
damage_buffer_height = grd_get_aligned_size (surface_height, TILE_HEIGHT) /
TILE_HEIGHT;
damage_buffer_length = damage_buffer_width * damage_buffer_height;
damage_detector->damage_buffer_width = damage_buffer_width;
damage_detector->damage_buffer_height = damage_buffer_height;
damage_detector->damage_buffer = g_new0 (uint32_t, damage_buffer_length);
damage_detector->damage_buffer_length = damage_buffer_length;
}
GrdDamageDetectorSw *
grd_damage_detector_sw_new (uint32_t surface_width,
uint32_t surface_height)
{
GrdDamageDetectorSw *damage_detector;
damage_detector = g_object_new (GRD_TYPE_DAMAGE_DETECTOR_SW, NULL);
damage_detector->surface_width = surface_width;
damage_detector->surface_height = surface_height;
create_damage_buffer (damage_detector);
return damage_detector;
}
static void
grd_damage_detector_sw_dispose (GObject *object)
{
GrdDamageDetectorSw *damage_detector = GRD_DAMAGE_DETECTOR_SW (object);
g_clear_pointer (&damage_detector->damage_buffer, g_free);
G_OBJECT_CLASS (grd_damage_detector_sw_parent_class)->dispose (object);
}
static void
grd_damage_detector_sw_init (GrdDamageDetectorSw *damage_detector)
{
}
static void
grd_damage_detector_sw_class_init (GrdDamageDetectorSwClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_damage_detector_sw_dispose;
}

40
grd-damage-detector-sw.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_DAMAGE_DETECTOR_SW (grd_damage_detector_sw_get_type ())
G_DECLARE_FINAL_TYPE (GrdDamageDetectorSw, grd_damage_detector_sw,
GRD, DAMAGE_DETECTOR_SW, GObject)
GrdDamageDetectorSw *grd_damage_detector_sw_new (uint32_t surface_width,
uint32_t surface_height);
uint32_t *grd_damage_detector_sw_get_damage_buffer (GrdDamageDetectorSw *damage_detector);
uint32_t grd_damage_detector_sw_get_damage_buffer_length (GrdDamageDetectorSw *damage_detector);
void grd_damage_detector_sw_compute_damage (GrdDamageDetectorSw *damage_detector,
GrdLocalBuffer *local_buffer_new,
GrdLocalBuffer *local_buffer_old);

92
grd-damage-utils.c Normal file
View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 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-damage-utils.h"
#include <string.h>
bool
grd_is_tile_dirty (cairo_rectangle_int_t *tile,
uint8_t *current_data,
uint8_t *prev_data,
uint32_t stride,
uint32_t bytes_per_pixel)
{
uint32_t y;
for (y = tile->y; y < tile->y + tile->height; ++y)
{
if (memcmp (prev_data + y * stride + tile->x * bytes_per_pixel,
current_data + y * stride + tile->x * bytes_per_pixel,
tile->width * bytes_per_pixel))
return true;
}
return false;
}
cairo_region_t *
grd_get_damage_region (uint8_t *current_data,
uint8_t *prev_data,
uint32_t surface_width,
uint32_t surface_height,
uint32_t tile_width,
uint32_t tile_height,
uint32_t stride,
uint32_t bytes_per_pixel)
{
cairo_region_t *damage_region;
cairo_rectangle_int_t tile;
uint32_t cols, rows;
uint32_t x, y;
damage_region = cairo_region_create ();
if (current_data == NULL || prev_data == NULL)
{
tile.x = tile.y = 0;
tile.width = surface_width;
tile.height = surface_height;
cairo_region_union_rectangle (damage_region, &tile);
return damage_region;
}
cols = surface_width / tile_width + (surface_width % tile_width ? 1 : 0);
rows = surface_height / tile_height + (surface_height % tile_height ? 1 : 0);
for (y = 0; y < rows; ++y)
{
for (x = 0; x < cols; ++x)
{
tile.x = x * tile_width;
tile.y = y * tile_height;
tile.width = surface_width - tile.x < tile_width ? surface_width - tile.x
: tile_width;
tile.height = surface_height - tile.y < tile_height ? surface_height - tile.y
: tile_height;
if (grd_is_tile_dirty (&tile, current_data, prev_data, stride, bytes_per_pixel))
cairo_region_union_rectangle (damage_region, &tile);
}
}
return damage_region;
}

39
grd-damage-utils.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include <cairo/cairo.h>
#include <stdbool.h>
#include <stdint.h>
cairo_region_t *grd_get_damage_region (uint8_t *current_data,
uint8_t *prev_data,
uint32_t surface_width,
uint32_t surface_height,
uint32_t tile_width,
uint32_t tile_height,
uint32_t stride,
uint32_t bytes_per_pixel);
bool grd_is_tile_dirty (cairo_rectangle_int_t *tile,
uint8_t *current_data,
uint8_t *prev_data,
uint32_t stride,
uint32_t bytes_per_pixel);

62
grd-debug.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2015,2022 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 "grd-debug.h"
#include <glib.h>
static const GDebugKey grd_debug_keys[] = {
{ "vnc", GRD_DEBUG_VNC },
{ "tpm", GRD_DEBUG_TPM },
{ "vk-validation", GRD_DEBUG_VK_VALIDATION },
{ "vk-times", GRD_DEBUG_VK_TIMES },
{ "va-times", GRD_DEBUG_VA_TIMES },
{ "vkva-renderer", GRD_DEBUG_VKVA },
};
static GrdDebugFlags debug_flags;
static gpointer
init_debug_flags (gpointer user_data)
{
const char *debug_env;
debug_env = g_getenv ("GNOME_REMOTE_DESKTOP_DEBUG");
if (debug_env)
{
debug_flags = g_parse_debug_string (debug_env,
grd_debug_keys,
G_N_ELEMENTS (grd_debug_keys));
}
return NULL;
}
GrdDebugFlags
grd_get_debug_flags (void)
{
static GOnce debug_flags_once = G_ONCE_INIT;
g_once (&debug_flags_once, init_debug_flags, NULL);
return debug_flags;
}

34
grd-debug.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2015,2022 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.
*
*/
#pragma once
typedef enum _GrdDebugFlags
{
GRD_DEBUG_NONE = 0,
GRD_DEBUG_VNC = 1 << 0,
GRD_DEBUG_TPM = 1 << 1,
GRD_DEBUG_VK_VALIDATION = 1 << 2,
GRD_DEBUG_VK_TIMES = 1 << 3,
GRD_DEBUG_VA_TIMES = 1 << 4,
GRD_DEBUG_VKVA = 1 << 5,
} GrdDebugFlags;
GrdDebugFlags grd_get_debug_flags (void);

422
grd-decode-session-sw-avc.c Normal file
View File

@@ -0,0 +1,422 @@
/*
* Copyright (C) 2025 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-decode-session-sw-avc.h"
#include <fcntl.h>
#include <freerdp/codec/color.h>
#include <freerdp/codec/h264.h>
#include <glib/gstdio.h>
#include <sys/mman.h>
#include "grd-sample-buffer.h"
#include "grd-utils.h"
#define MAX_BASE_SIZE (1 << 18)
typedef struct
{
uint8_t *data;
GrdSampleBuffer *sample_buffer;
} BitstreamBuffer;
struct _GrdDecodeSessionSwAVC
{
GrdDecodeSession parent;
H264_CONTEXT *h264_context;
uint32_t surface_width;
uint32_t surface_height;
GHashTable *bitstream_buffers;
GHashTable *pipewire_buffers;
GMutex pending_frames_mutex;
GHashTable *pending_frames;
};
G_DEFINE_TYPE (GrdDecodeSessionSwAVC, grd_decode_session_sw_avc,
GRD_TYPE_DECODE_SESSION)
static BitstreamBuffer *
bitstream_buffer_new (size_t size)
{
BitstreamBuffer *bitstream_buffer;
bitstream_buffer = g_new0 (BitstreamBuffer, 1);
bitstream_buffer->data = g_malloc0 (size);
bitstream_buffer->sample_buffer =
grd_sample_buffer_new (bitstream_buffer->data, size);
return bitstream_buffer;
}
static void
bitstream_buffer_free (BitstreamBuffer *bitstream_buffer)
{
g_clear_pointer (&bitstream_buffer->sample_buffer, grd_sample_buffer_free);
g_clear_pointer (&bitstream_buffer->data, g_free);
g_free (bitstream_buffer);
}
static void
grd_decode_session_sw_avc_get_drm_format_modifiers (GrdDecodeSession *decode_session,
uint32_t drm_format,
uint32_t *out_n_modifiers,
uint64_t **out_modifiers)
{
*out_n_modifiers = 0;
*out_modifiers = NULL;
}
static gboolean
grd_decode_session_sw_avc_reset (GrdDecodeSession *decode_session,
uint32_t surface_width,
uint32_t surface_height,
uint64_t drm_format_modifier,
GError **error)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
decode_session_sw_avc->surface_width = surface_width;
decode_session_sw_avc->surface_height = surface_height;
if (!h264_context_reset (decode_session_sw_avc->h264_context, surface_width, surface_height))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to reset H.264 decode context");
return FALSE;
}
return TRUE;
}
static gboolean
allocate_mem_fd_buffer (GrdDecodeSessionSwAVC *decode_session_sw_avc,
struct pw_buffer *pw_buffer,
GError **error)
{
uint32_t surface_width = decode_session_sw_avc->surface_width;
uint32_t surface_height = decode_session_sw_avc->surface_height;
struct spa_data *spa_data = &pw_buffer->buffer->datas[0];
g_autofd int fd = -1;
unsigned int seals;
int ret;
fd = memfd_create ("grd-decode-session-mem-fd",
MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd == -1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create mem-fd: %s", strerror (errno));
return FALSE;
}
spa_data->chunk->stride = surface_width * 4;
spa_data->maxsize = spa_data->chunk->stride * surface_height;
spa_data->mapoffset = 0;
do
ret = ftruncate (fd, spa_data->maxsize);
while (ret == -1 && errno == EINTR);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to truncate mem-fd: %s", strerror (errno));
return FALSE;
}
seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
if (fcntl (fd, F_ADD_SEALS, seals) < 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to add seals to mem-fd: %s", strerror (errno));
return FALSE;
}
spa_data->data = mmap (NULL, spa_data->maxsize,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
spa_data->mapoffset);
if (spa_data->data == MAP_FAILED)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to mmap memory: %s", strerror (errno));
return FALSE;
}
spa_data->type = SPA_DATA_MemFd;
spa_data->flags = SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_MAPPABLE;
spa_data->fd = g_steal_fd (&fd);
return TRUE;
}
static gboolean
grd_decode_session_sw_avc_register_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer,
GError **error)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
uint32_t surface_width = decode_session_sw_avc->surface_width;
uint32_t surface_height = decode_session_sw_avc->surface_height;
BitstreamBuffer *bitstream_buffer;
size_t width_in_mbs;
size_t height_in_mbs;
size_t size;
if (!allocate_mem_fd_buffer (decode_session_sw_avc, pw_buffer, error))
return FALSE;
width_in_mbs = grd_get_aligned_size (surface_width, 16) / 16;
height_in_mbs = grd_get_aligned_size (surface_height, 16) / 16;
size = MAX_BASE_SIZE + width_in_mbs * height_in_mbs * 400;
bitstream_buffer = bitstream_buffer_new (size);
g_hash_table_insert (decode_session_sw_avc->bitstream_buffers,
pw_buffer, bitstream_buffer);
g_hash_table_insert (decode_session_sw_avc->pipewire_buffers,
bitstream_buffer->sample_buffer, pw_buffer);
return TRUE;
}
static void
destroy_mem_fd_buffer (GrdDecodeSessionSwAVC *decode_session_sw_avc,
struct pw_buffer *pw_buffer)
{
struct spa_data *spa_data = &pw_buffer->buffer->datas[0];
if (spa_data->type != SPA_DATA_MemFd)
return;
g_assert (spa_data->data != MAP_FAILED);
g_assert (spa_data->fd > 0);
munmap (spa_data->data, spa_data->maxsize);
close (spa_data->fd);
}
static void
grd_decode_session_sw_avc_unregister_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
BitstreamBuffer *bitstream_buffer = NULL;
GrdSampleBuffer *sample_buffer;
if (!g_hash_table_lookup_extended (decode_session_sw_avc->bitstream_buffers,
pw_buffer,
NULL, (gpointer *) &bitstream_buffer))
return;
sample_buffer = bitstream_buffer->sample_buffer;
g_hash_table_remove (decode_session_sw_avc->bitstream_buffers, pw_buffer);
g_hash_table_remove (decode_session_sw_avc->pipewire_buffers, sample_buffer);
destroy_mem_fd_buffer (decode_session_sw_avc, pw_buffer);
}
static GrdSampleBuffer *
grd_decode_session_sw_avc_get_sample_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
BitstreamBuffer *bitstream_buffer = NULL;
if (!g_hash_table_lookup_extended (decode_session_sw_avc->bitstream_buffers,
pw_buffer,
NULL, (gpointer *) &bitstream_buffer))
g_assert_not_reached ();
return bitstream_buffer->sample_buffer;
}
static gboolean
grd_decode_session_sw_avc_submit_sample (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GError **error)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
g_mutex_lock (&decode_session_sw_avc->pending_frames_mutex);
g_assert (!g_hash_table_contains (decode_session_sw_avc->pending_frames,
sample_buffer));
g_hash_table_add (decode_session_sw_avc->pending_frames, sample_buffer);
g_mutex_unlock (&decode_session_sw_avc->pending_frames_mutex);
return TRUE;
}
static uint32_t
grd_decode_session_sw_avc_get_n_pending_frames (GrdDecodeSession *decode_session)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&decode_session_sw_avc->pending_frames_mutex);
return g_hash_table_size (decode_session_sw_avc->pending_frames);
}
static gboolean
grd_decode_session_sw_avc_decode_frame (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GError **error)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (decode_session);
uint32_t surface_width = decode_session_sw_avc->surface_width;
uint32_t surface_height = decode_session_sw_avc->surface_height;
struct pw_buffer *pw_buffer = NULL;
struct spa_data *spa_data;
RECTANGLE_16 rect = {};
int32_t ret;
g_mutex_lock (&decode_session_sw_avc->pending_frames_mutex);
if (!g_hash_table_remove (decode_session_sw_avc->pending_frames,
sample_buffer))
g_assert_not_reached ();
g_mutex_unlock (&decode_session_sw_avc->pending_frames_mutex);
if (!g_hash_table_lookup_extended (decode_session_sw_avc->pipewire_buffers,
sample_buffer,
NULL, (gpointer *) &pw_buffer))
g_assert_not_reached ();
spa_data = &pw_buffer->buffer->datas[0];
rect.left = 0;
rect.top = 0;
rect.right = surface_width;
rect.bottom = surface_height;
ret = avc420_decompress (decode_session_sw_avc->h264_context,
grd_sample_buffer_get_data_pointer (sample_buffer),
grd_sample_buffer_get_sample_size (sample_buffer),
spa_data->data, PIXEL_FORMAT_BGRA32,
spa_data->chunk->stride, surface_width, surface_height,
&rect, 1);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"avc420_decompress failed with status code %i", -ret);
return FALSE;
}
return TRUE;
}
GrdDecodeSessionSwAVC *
grd_decode_session_sw_avc_new (GError **error)
{
g_autoptr (GrdDecodeSessionSwAVC) decode_session_sw_avc = NULL;
decode_session_sw_avc = g_object_new (GRD_TYPE_DECODE_SESSION_SW_AVC, NULL);
decode_session_sw_avc->h264_context = h264_context_new (FALSE);
if (!decode_session_sw_avc->h264_context)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create H.264 decode context");
return NULL;
}
return g_steal_pointer (&decode_session_sw_avc);
}
static void
grd_decode_session_sw_avc_dispose (GObject *object)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (object);
g_clear_pointer (&decode_session_sw_avc->h264_context, h264_context_free);
g_clear_pointer (&decode_session_sw_avc->pending_frames, g_hash_table_unref);
g_clear_pointer (&decode_session_sw_avc->pipewire_buffers, g_hash_table_unref);
g_clear_pointer (&decode_session_sw_avc->bitstream_buffers, g_hash_table_unref);
G_OBJECT_CLASS (grd_decode_session_sw_avc_parent_class)->dispose (object);
}
static void
grd_decode_session_sw_avc_finalize (GObject *object)
{
GrdDecodeSessionSwAVC *decode_session_sw_avc =
GRD_DECODE_SESSION_SW_AVC (object);
g_mutex_clear (&decode_session_sw_avc->pending_frames_mutex);
G_OBJECT_CLASS (grd_decode_session_sw_avc_parent_class)->finalize (object);
}
static void
grd_decode_session_sw_avc_init (GrdDecodeSessionSwAVC *decode_session_sw_avc)
{
decode_session_sw_avc->bitstream_buffers =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) bitstream_buffer_free);
decode_session_sw_avc->pipewire_buffers = g_hash_table_new (NULL, NULL);
decode_session_sw_avc->pending_frames = g_hash_table_new (NULL, NULL);
g_mutex_init (&decode_session_sw_avc->pending_frames_mutex);
}
static void
grd_decode_session_sw_avc_class_init (GrdDecodeSessionSwAVCClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdDecodeSessionClass *decode_session_class =
GRD_DECODE_SESSION_CLASS (klass);
object_class->dispose = grd_decode_session_sw_avc_dispose;
object_class->finalize = grd_decode_session_sw_avc_finalize;
decode_session_class->get_drm_format_modifiers =
grd_decode_session_sw_avc_get_drm_format_modifiers;
decode_session_class->reset =
grd_decode_session_sw_avc_reset;
decode_session_class->register_buffer =
grd_decode_session_sw_avc_register_buffer;
decode_session_class->unregister_buffer =
grd_decode_session_sw_avc_unregister_buffer;
decode_session_class->get_sample_buffer =
grd_decode_session_sw_avc_get_sample_buffer;
decode_session_class->submit_sample =
grd_decode_session_sw_avc_submit_sample;
decode_session_class->get_n_pending_frames =
grd_decode_session_sw_avc_get_n_pending_frames;
decode_session_class->decode_frame =
grd_decode_session_sw_avc_decode_frame;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include "grd-decode-session.h"
#define GRD_TYPE_DECODE_SESSION_SW_AVC (grd_decode_session_sw_avc_get_type ())
G_DECLARE_FINAL_TYPE (GrdDecodeSessionSwAVC, grd_decode_session_sw_avc,
GRD, DECODE_SESSION_SW_AVC, GrdDecodeSession)
GrdDecodeSessionSwAVC *grd_decode_session_sw_avc_new (GError **error);

267
grd-decode-session.c Normal file
View File

@@ -0,0 +1,267 @@
/*
* Copyright (C) 2025 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-decode-session.h"
typedef struct
{
GrdSampleBuffer *sample_buffer;
GrdDecodeSessionOnFrameReadyFunc callback;
gpointer callback_user_data;
} GrdDecodeFrameTask;
typedef struct
{
gboolean in_shutdown;
GThread *decode_thread;
GMainContext *decode_context;
GSource *frame_decode_source;
GAsyncQueue *task_queue;
} GrdDecodeSessionPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GrdDecodeSession, grd_decode_session,
G_TYPE_OBJECT)
void
grd_decode_session_get_drm_format_modifiers (GrdDecodeSession *decode_session,
uint32_t drm_format,
uint32_t *out_n_modifiers,
uint64_t **out_modifiers)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
klass->get_drm_format_modifiers (decode_session, drm_format,
out_n_modifiers, out_modifiers);
}
gboolean
grd_decode_session_reset (GrdDecodeSession *decode_session,
uint32_t surface_width,
uint32_t surface_height,
uint64_t drm_format_modifier,
GError **error)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
return klass->reset (decode_session, surface_width, surface_height,
drm_format_modifier, error);
}
gboolean
grd_decode_session_register_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer,
GError **error)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
return klass->register_buffer (decode_session, pw_buffer, error);
}
void
grd_decode_session_unregister_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
return klass->unregister_buffer (decode_session, pw_buffer);
}
GrdSampleBuffer *
grd_decode_session_get_sample_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
return klass->get_sample_buffer (decode_session, pw_buffer);
}
uint32_t
grd_decode_session_get_n_pending_frames (GrdDecodeSession *decode_session)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
return klass->get_n_pending_frames (decode_session);
}
gboolean
grd_decode_session_decode_frame (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GrdDecodeSessionOnFrameReadyFunc on_frame_ready,
gpointer user_data,
GError **error)
{
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
GrdDecodeFrameTask *task;
if (!klass->submit_sample (decode_session, sample_buffer, error))
return FALSE;
task = g_new0 (GrdDecodeFrameTask, 1);
task->sample_buffer = sample_buffer;
task->callback = on_frame_ready;
task->callback_user_data = user_data;
g_async_queue_push (priv->task_queue, task);
g_source_set_ready_time (priv->frame_decode_source, 0);
return TRUE;
}
static void
stop_decode_thread (GrdDecodeSession *decode_session)
{
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
g_assert (priv->decode_context);
g_assert (priv->decode_thread);
priv->in_shutdown = TRUE;
g_main_context_wakeup (priv->decode_context);
g_clear_pointer (&priv->decode_thread, g_thread_join);
}
static void
grd_decode_session_dispose (GObject *object)
{
GrdDecodeSession *decode_session = GRD_DECODE_SESSION (object);
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
g_assert (g_async_queue_try_pop (priv->task_queue) == NULL);
if (priv->decode_thread)
stop_decode_thread (decode_session);
if (priv->frame_decode_source)
{
g_source_destroy (priv->frame_decode_source);
g_clear_pointer (&priv->frame_decode_source, g_source_unref);
}
g_clear_pointer (&priv->decode_context, g_main_context_unref);
G_OBJECT_CLASS (grd_decode_session_parent_class)->dispose (object);
}
static void
grd_decode_session_finalize (GObject *object)
{
GrdDecodeSession *decode_session = GRD_DECODE_SESSION (object);
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
g_clear_pointer (&priv->task_queue, g_async_queue_unref);
G_OBJECT_CLASS (grd_decode_session_parent_class)->finalize (object);
}
static gpointer
decode_thread_func (gpointer data)
{
GrdDecodeSession *decode_session = data;
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
while (!priv->in_shutdown)
g_main_context_iteration (priv->decode_context, TRUE);
return NULL;
}
static gboolean
decode_frames (gpointer user_data)
{
GrdDecodeSession *decode_session = user_data;
GrdDecodeSessionClass *klass = GRD_DECODE_SESSION_GET_CLASS (decode_session);
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
GrdDecodeFrameTask *task;
while ((task = g_async_queue_try_pop (priv->task_queue)))
{
gboolean success;
g_autoptr (GError) error = NULL;
success = klass->decode_frame (decode_session, task->sample_buffer,
&error);
g_assert (success == !error);
task->callback (decode_session, task->sample_buffer,
task->callback_user_data, error);
g_free (task);
}
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_decode_session_init (GrdDecodeSession *decode_session)
{
GrdDecodeSessionPrivate *priv =
grd_decode_session_get_instance_private (decode_session);
GSource *frame_decode_source;
priv->task_queue = g_async_queue_new ();
priv->decode_context = g_main_context_new ();
priv->decode_thread = g_thread_new ("Decode thread",
decode_thread_func,
decode_session);
frame_decode_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (frame_decode_source, decode_frames,
decode_session, NULL);
g_source_set_ready_time (frame_decode_source, -1);
g_source_attach (frame_decode_source, priv->decode_context);
priv->frame_decode_source = frame_decode_source;
}
static void
grd_decode_session_class_init (GrdDecodeSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_decode_session_dispose;
object_class->finalize = grd_decode_session_finalize;
}

92
grd-decode-session.h Normal file
View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <glib-object.h>
#include <pipewire/pipewire.h>
#include "grd-types.h"
#define GRD_TYPE_DECODE_SESSION (grd_decode_session_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdDecodeSession, grd_decode_session,
GRD, DECODE_SESSION, GObject)
typedef void (* GrdDecodeSessionOnFrameReadyFunc) (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
gpointer user_data,
GError *error);
struct _GrdDecodeSessionClass
{
GObjectClass parent_class;
void (* get_drm_format_modifiers) (GrdDecodeSession *decode_session,
uint32_t drm_format,
uint32_t *out_n_modifiers,
uint64_t **out_modifiers);
gboolean (* reset) (GrdDecodeSession *decode_session,
uint32_t surface_width,
uint32_t surface_height,
uint64_t drm_format_modifier,
GError **error);
gboolean (* register_buffer) (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer,
GError **error);
void (* unregister_buffer) (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer);
GrdSampleBuffer *(* get_sample_buffer) (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer);
gboolean (* submit_sample) (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GError **error);
uint32_t (* get_n_pending_frames) (GrdDecodeSession *decode_session);
gboolean (* decode_frame) (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GError **error);
};
void grd_decode_session_get_drm_format_modifiers (GrdDecodeSession *decode_session,
uint32_t drm_format,
uint32_t *out_n_modifiers,
uint64_t **out_modifiers);
gboolean grd_decode_session_reset (GrdDecodeSession *decode_session,
uint32_t surface_width,
uint32_t surface_height,
uint64_t drm_format_modifier,
GError **error);
gboolean grd_decode_session_register_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer,
GError **error);
void grd_decode_session_unregister_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer);
GrdSampleBuffer *grd_decode_session_get_sample_buffer (GrdDecodeSession *decode_session,
struct pw_buffer *pw_buffer);
uint32_t grd_decode_session_get_n_pending_frames (GrdDecodeSession *decode_session);
gboolean grd_decode_session_decode_frame (GrdDecodeSession *decode_session,
GrdSampleBuffer *sample_buffer,
GrdDecodeSessionOnFrameReadyFunc on_frame_ready,
gpointer user_data,
GError **error);

1325
grd-egl-thread.c Normal file

File diff suppressed because it is too large Load Diff

114
grd-egl-thread.h Normal file
View File

@@ -0,0 +1,114 @@
/*
* 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.
*
*/
#pragma once
#include <glib.h>
#include <stdint.h>
#include "grd-types.h"
typedef gpointer GrdEglThreadSlot;
typedef void (* GrdEglThreadCallback) (gboolean success,
gpointer user_data);
typedef gboolean (* GrdEglThreadCustomFunc) (gpointer user_data);
typedef gboolean (* GrdEglThreadAllocBufferFunc) (gpointer user_data,
uint32_t pbo);
typedef void (* GrdEglThreadDeallocBufferFunc) (gpointer user_data);
GrdEglThread * grd_egl_thread_new (GError **error);
void grd_egl_thread_free (GrdEglThread *egl_thread);
const char * grd_egl_thread_get_drm_render_node (GrdEglThread *egl_thread);
void * grd_egl_thread_acquire_slot (GrdEglThread *egl_thread);
void grd_egl_thread_release_slot (GrdEglThread *egl_thread,
GrdEglThreadSlot slot);
void grd_egl_thread_download (GrdEglThread *egl_thread,
GrdEglThreadSlot slot,
uint8_t *dst_data,
int dst_row_width,
uint32_t format,
unsigned int width,
unsigned int height,
uint32_t n_planes,
const int *fds,
const uint32_t *strides,
const uint32_t *offsets,
const uint64_t *modifiers,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
void grd_egl_thread_allocate (GrdEglThread *egl_thread,
uint32_t height,
uint32_t stride,
GrdEglThreadAllocBufferFunc allocate_func,
gpointer allocate_user_data,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
void grd_egl_thread_deallocate (GrdEglThread *egl_thread,
uint32_t pbo,
GrdEglThreadDeallocBufferFunc deallocate_func,
gpointer deallocate_user_data,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
void grd_egl_thread_upload (GrdEglThread *egl_thread,
GrdEglThreadSlot slot,
uint32_t pbo,
uint32_t height,
uint32_t stride,
uint8_t *src_data,
GrdEglThreadAllocBufferFunc allocate_func,
gpointer allocate_user_data,
GDestroyNotify allocate_user_data_destroy,
GrdEglThreadCustomFunc realize_func,
gpointer realize_user_data,
GDestroyNotify realize_user_data_destroy,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
void grd_egl_thread_sync (GrdEglThread *egl_thread,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
void grd_egl_thread_run_custom_task (GrdEglThread *egl_thread,
GrdEglThreadCustomFunc custom_func,
gpointer custom_func_data,
GrdEglThreadCallback callback,
gpointer user_data,
GDestroyNotify destroy);
gboolean grd_egl_thread_get_modifiers_for_format (GrdEglThread *egl_thread,
uint32_t format,
int *out_n_modifiers,
uint64_t **out_modifiers);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrdEglThread, grd_egl_thread_free)

261
grd-enable-service.c Normal file
View File

@@ -0,0 +1,261 @@
#include <config.h>
#include <gio/gio.h>
#include <limits.h>
#include <polkit/polkit.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include "grd-enums.h"
#define GRD_SYSTEMD_SERVICE "gnome-remote-desktop.service"
#define GRD_SYSTEMD_HEADLESS_SERVICE "gnome-remote-desktop-headless.service"
#define GRD_POLKIT_ACTION "org.gnome.remotedesktop.configure-system-daemon"
static gboolean
get_runtime_mode (const char *argv,
GrdRuntimeMode *runtime_mode)
{
if (g_str_equal (argv, "user"))
*runtime_mode = GRD_RUNTIME_MODE_SCREEN_SHARE;
else if (g_str_equal (argv, "headless"))
*runtime_mode = GRD_RUNTIME_MODE_HEADLESS;
else if (g_str_equal (argv, "system"))
*runtime_mode = GRD_RUNTIME_MODE_SYSTEM;
else
return FALSE;
return TRUE;
}
static gboolean
check_polkit_permission (const char *action_id,
pid_t pid,
GError **error)
{
g_autoptr (GPermission) permission = NULL;
g_autoptr (PolkitSubject) subject = NULL;
subject = polkit_unix_process_new_for_owner (pid, 0, -1);
permission = polkit_permission_new_sync (action_id, subject, NULL, error);
if (!permission)
return FALSE;
if (!g_permission_get_allowed (permission))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_PERMISSION_DENIED,
"This program requires callers to have access to the %s "
"polkit action", action_id);
return FALSE;
}
return TRUE;
}
static gboolean
validate_caller (int64_t pid)
{
g_autoptr (GError) error = NULL;
if (geteuid () != 0)
{
g_printerr ("Enabling system service is not meant to be done directly by users.\n");
return FALSE;
}
if (!check_polkit_permission (GRD_POLKIT_ACTION, (pid_t) pid, &error))
{
g_printerr ("Authorization failed for " GRD_POLKIT_ACTION ": %s\n",
error->message);
return FALSE;
}
return TRUE;
}
static void
get_systemd_unit_info (GrdRuntimeMode runtime_mode,
GBusType *bus_type,
const char **unit)
{
switch (runtime_mode)
{
case GRD_RUNTIME_MODE_HEADLESS:
*bus_type = G_BUS_TYPE_SESSION;
*unit = GRD_SYSTEMD_HEADLESS_SERVICE;
break;
case GRD_RUNTIME_MODE_SYSTEM:
*bus_type = G_BUS_TYPE_SYSTEM;
*unit = GRD_SYSTEMD_SERVICE;
break;
case GRD_RUNTIME_MODE_SCREEN_SHARE:
*bus_type = G_BUS_TYPE_SESSION;
*unit = GRD_SYSTEMD_SERVICE;
break;
default:
g_assert_not_reached ();
}
}
static gboolean
enable_systemd_unit (GrdRuntimeMode runtime_mode,
GError **error)
{
g_autoptr (GDBusConnection) connection = NULL;
g_autoptr (GVariant) reply = NULL;
GBusType bus_type;
const char *unit;
get_systemd_unit_info (runtime_mode, &bus_type, &unit);
connection = g_bus_get_sync (bus_type, NULL, error);
if (!connection)
return FALSE;
reply = g_dbus_connection_call_sync (connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"StartUnit",
g_variant_new ("(ss)",
unit, "replace"),
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1, NULL, error);
if (!reply)
return FALSE;
reply = g_dbus_connection_call_sync (connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"EnableUnitFiles",
g_variant_new ("(^asbb)",
(const char*[])
{ unit, NULL },
FALSE, FALSE),
(GVariantType *) "(ba(sss))",
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
return reply != NULL;
}
static gboolean
disable_systemd_unit (GrdRuntimeMode runtime_mode,
GError **error)
{
g_autoptr (GDBusConnection) connection = NULL;
g_autoptr (GVariant) reply = NULL;
GBusType bus_type;
const char *unit;
get_systemd_unit_info (runtime_mode, &bus_type, &unit);
connection = g_bus_get_sync (bus_type, NULL, error);
if (!connection)
return FALSE;
reply = g_dbus_connection_call_sync (connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"StopUnit",
g_variant_new ("(ss)",
unit, "replace"),
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1, NULL, error);
if (!reply)
return FALSE;
reply = g_dbus_connection_call_sync (connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"DisableUnitFiles",
g_variant_new ("(^asb)",
(const char*[])
{ unit, NULL },
FALSE),
(GVariantType *) "(a(sss))",
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
return reply != NULL;
}
static void
print_usage (void)
{
g_printerr ("Usage: %s pid <user|headless|system> <true|false>\n", g_get_prgname ());
}
int
main (int argc,
char *argv[])
{
g_autoptr (GError) error = NULL;
GrdRuntimeMode runtime_mode;
gboolean success;
gboolean enable;
int64_t pid;
g_set_prgname (argv[0]);
if (argc != 4)
{
print_usage ();
return EXIT_FAILURE;
}
success = g_ascii_string_to_signed (argv[1], 10, 1, INT_MAX, &pid, NULL);
if (!success)
{
print_usage ();
return EXIT_FAILURE;
}
success = get_runtime_mode (argv[2], &runtime_mode);
if (!success)
{
print_usage ();
return EXIT_FAILURE;
}
enable = g_strcmp0 (argv[3], "true") == 0;
if (!enable && g_strcmp0 (argv[3], "false") != 0)
{
print_usage ();
return EXIT_FAILURE;
}
if (runtime_mode == GRD_RUNTIME_MODE_SYSTEM)
{
success = validate_caller (pid);
if (!success)
return EXIT_FAILURE;
}
if (enable)
success = enable_systemd_unit (runtime_mode, &error);
else
success = disable_systemd_unit (runtime_mode, &error);
if (!success)
{
g_printerr ("Failed to %s service: %s\n",
enable ? "enable" : "disable", error->message);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

56
grd-encode-context.c Normal file
View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2024 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-encode-context.h"
#include <glib.h>
struct _GrdEncodeContext
{
cairo_region_t *damage_region;
};
cairo_region_t *
grd_encode_context_get_damage_region (GrdEncodeContext *encode_context)
{
return encode_context->damage_region;
}
void
grd_encode_context_set_damage_region (GrdEncodeContext *encode_context,
cairo_region_t *damage_region)
{
encode_context->damage_region = cairo_region_reference (damage_region);
}
GrdEncodeContext *
grd_encode_context_new (void)
{
return g_new0 (GrdEncodeContext, 1);
}
void
grd_encode_context_free (GrdEncodeContext *encode_context)
{
g_clear_pointer (&encode_context->damage_region, cairo_region_destroy);
g_free (encode_context);
}

33
grd-encode-context.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <cairo/cairo.h>
#include "grd-types.h"
GrdEncodeContext *grd_encode_context_new (void);
void grd_encode_context_free (GrdEncodeContext *encode_context);
cairo_region_t *grd_encode_context_get_damage_region (GrdEncodeContext *encode_context);
void grd_encode_context_set_damage_region (GrdEncodeContext *encode_context,
cairo_region_t *damage_region);

406
grd-encode-session-ca-sw.c Normal file
View File

@@ -0,0 +1,406 @@
/*
* Copyright (C) 2024 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-encode-session-ca-sw.h"
#include <gio/gio.h>
#include "grd-bitstream.h"
#include "grd-encode-context.h"
#include "grd-image-view-rgb.h"
#include "grd-local-buffer.h"
#include "grd-rdp-sw-encoder-ca.h"
/*
* One surface is needed, when encoding a frame,
* one is needed, when preparing the view,
* one is needed for the submitted pending frame in the renderer,
* and one is needed to prepare a new pending frame before it is submitted to
* the renderer, where it then replaces the old pending frame
*
* In total, this makes four needed source surfaces
*/
#define N_SRC_SURFACES 4
/*
* One encode stream is needed, when submitting a frame,
* one is needed, when encoding a frame
*
* In total, this makes two needed encode streams
*/
#define N_ENCODE_STREAMS 2
#define INITIAL_STREAM_SIZE 16384
typedef struct
{
GrdImageView *image_view;
} RGBSurface;
struct _GrdEncodeSessionCaSw
{
GrdEncodeSession parent;
GrdRdpSwEncoderCa *encoder_ca;
uint32_t surface_width;
uint32_t surface_height;
GHashTable *surfaces;
GMutex pending_encodes_mutex;
GHashTable *pending_encodes;
GMutex acquired_encode_streams_mutex;
wStream *encode_streams[N_ENCODE_STREAMS];
GHashTable *acquired_encode_streams;
GMutex bitstreams_mutex;
GHashTable *bitstreams;
gboolean pending_header;
};
G_DEFINE_TYPE (GrdEncodeSessionCaSw, grd_encode_session_ca_sw,
GRD_TYPE_ENCODE_SESSION)
static RGBSurface *
rgb_surface_new (void)
{
RGBSurface *rgb_surface = NULL;
GrdImageViewRGB *image_view_rgb;
rgb_surface = g_new0 (RGBSurface, 1);
image_view_rgb = grd_image_view_rgb_new ();
rgb_surface->image_view = GRD_IMAGE_VIEW (image_view_rgb);
return rgb_surface;
}
static void
rgb_surface_free (RGBSurface *rgb_surface)
{
g_clear_object (&rgb_surface->image_view);
g_free (rgb_surface);
}
static void
grd_encode_session_ca_sw_get_surface_size (GrdEncodeSession *encode_session,
uint32_t *surface_width,
uint32_t *surface_height)
{
g_assert_not_reached ();
}
static GList *
grd_encode_session_ca_sw_get_image_views (GrdEncodeSession *encode_session)
{
GrdEncodeSessionCaSw *encode_session_ca =
GRD_ENCODE_SESSION_CA_SW (encode_session);
return g_hash_table_get_keys (encode_session_ca->surfaces);
}
static gboolean
grd_encode_session_ca_sw_has_pending_frames (GrdEncodeSession *encode_session)
{
GrdEncodeSessionCaSw *encode_session_ca =
GRD_ENCODE_SESSION_CA_SW (encode_session);
g_mutex_lock (&encode_session_ca->pending_encodes_mutex);
g_assert (g_hash_table_size (encode_session_ca->pending_encodes) == 0);
g_mutex_unlock (&encode_session_ca->pending_encodes_mutex);
return FALSE;
}
static gboolean
grd_encode_session_ca_sw_encode_frame (GrdEncodeSession *encode_session,
GrdEncodeContext *encode_context,
GrdImageView *image_view,
GError **error)
{
GrdEncodeSessionCaSw *encode_session_ca =
GRD_ENCODE_SESSION_CA_SW (encode_session);
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&encode_session_ca->pending_encodes_mutex);
g_assert (!g_hash_table_contains (encode_session_ca->pending_encodes,
image_view));
g_hash_table_insert (encode_session_ca->pending_encodes,
image_view, encode_context);
return TRUE;
}
static wStream *
acquire_encode_stream (GrdEncodeSessionCaSw *encode_session_ca)
{
g_autoptr (GMutexLocker) locker = NULL;
uint32_t i;
locker = g_mutex_locker_new (&encode_session_ca->acquired_encode_streams_mutex);
for (i = 0; i < N_ENCODE_STREAMS; ++i)
{
wStream *encode_stream = encode_session_ca->encode_streams[i];
if (g_hash_table_contains (encode_session_ca->acquired_encode_streams,
encode_stream))
continue;
g_hash_table_add (encode_session_ca->acquired_encode_streams,
encode_stream);
return encode_stream;
}
g_assert_not_reached ();
return NULL;
}
static void
release_encode_stream (GrdEncodeSessionCaSw *encode_session_ca,
wStream *encode_stream)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&encode_session_ca->acquired_encode_streams_mutex);
if (!g_hash_table_remove (encode_session_ca->acquired_encode_streams,
encode_stream))
g_assert_not_reached ();
}
static GrdBitstream *
grd_encode_session_ca_sw_lock_bitstream (GrdEncodeSession *encode_session,
GrdImageView *image_view,
GError **error)
{
GrdEncodeSessionCaSw *encode_session_ca =
GRD_ENCODE_SESSION_CA_SW (encode_session);
GrdImageViewRGB *image_view_rgb = GRD_IMAGE_VIEW_RGB (image_view);
GrdEncodeContext *encode_context = NULL;
g_autoptr (GMutexLocker) locker = NULL;
GrdLocalBuffer *local_buffer;
uint8_t *buffer;
uint32_t buffer_stride;
cairo_region_t *damage_region;
wStream *encode_stream;
GrdBitstream *bitstream;
locker = g_mutex_locker_new (&encode_session_ca->pending_encodes_mutex);
if (!g_hash_table_lookup_extended (encode_session_ca->pending_encodes,
image_view,
NULL, (gpointer *) &encode_context))
g_assert_not_reached ();
g_clear_pointer (&locker, g_mutex_locker_free);
g_assert (encode_context);
local_buffer = grd_image_view_rgb_get_local_buffer (image_view_rgb);
buffer = grd_local_buffer_get_buffer (local_buffer);
buffer_stride = grd_local_buffer_get_buffer_stride (local_buffer);
damage_region = grd_encode_context_get_damage_region (encode_context);
encode_stream = acquire_encode_stream (encode_session_ca);
grd_rdp_sw_encoder_ca_encode_progressive_frame (encode_session_ca->encoder_ca,
encode_session_ca->surface_width,
encode_session_ca->surface_height,
buffer,
buffer_stride,
damage_region,
encode_stream,
encode_session_ca->pending_header);
encode_session_ca->pending_header = FALSE;
bitstream = grd_bitstream_new (Stream_Buffer (encode_stream),
Stream_Length (encode_stream));
g_mutex_lock (&encode_session_ca->bitstreams_mutex);
g_hash_table_insert (encode_session_ca->bitstreams, bitstream, encode_stream);
g_mutex_unlock (&encode_session_ca->bitstreams_mutex);
locker = g_mutex_locker_new (&encode_session_ca->pending_encodes_mutex);
if (!g_hash_table_remove (encode_session_ca->pending_encodes, image_view))
g_assert_not_reached ();
return bitstream;
}
static gboolean
grd_encode_session_ca_sw_unlock_bitstream (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
GError **error)
{
GrdEncodeSessionCaSw *encode_session_ca =
GRD_ENCODE_SESSION_CA_SW (encode_session);
wStream *encode_stream = NULL;
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&encode_session_ca->bitstreams_mutex);
if (!g_hash_table_steal_extended (encode_session_ca->bitstreams,
bitstream,
NULL, (gpointer *) &encode_stream))
g_assert_not_reached ();
g_clear_pointer (&locker, g_mutex_locker_free);
g_assert (encode_stream);
grd_bitstream_free (bitstream);
release_encode_stream (encode_session_ca, encode_stream);
return TRUE;
}
static gboolean
init_surfaces_and_streams (GrdEncodeSessionCaSw *encode_session_ca,
GError **error)
{
uint32_t i;
for (i = 0; i < N_SRC_SURFACES; ++i)
{
RGBSurface *surface;
surface = rgb_surface_new ();
g_hash_table_insert (encode_session_ca->surfaces,
surface->image_view, surface);
}
for (i = 0; i < N_ENCODE_STREAMS; ++i)
{
wStream *encode_stream;
encode_stream = Stream_New (NULL, INITIAL_STREAM_SIZE);
if (!encode_stream)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create encode stream");
return FALSE;
}
encode_session_ca->encode_streams[i] = encode_stream;
}
return TRUE;
}
GrdEncodeSessionCaSw *
grd_encode_session_ca_sw_new (GrdRdpSwEncoderCa *encoder_ca,
uint32_t source_width,
uint32_t source_height,
GError **error)
{
g_autoptr (GrdEncodeSessionCaSw) encode_session_ca = NULL;
encode_session_ca = g_object_new (GRD_TYPE_ENCODE_SESSION_CA_SW, NULL);
encode_session_ca->encoder_ca = encoder_ca;
encode_session_ca->surface_width = source_width;
encode_session_ca->surface_height = source_height;
if (!init_surfaces_and_streams (encode_session_ca, error))
return NULL;
return g_steal_pointer (&encode_session_ca);
}
static void
encode_stream_free (wStream *encode_stream)
{
Stream_Free (encode_stream, TRUE);
}
static void
grd_encode_session_ca_sw_dispose (GObject *object)
{
GrdEncodeSessionCaSw *encode_session_ca = GRD_ENCODE_SESSION_CA_SW (object);
uint32_t i;
g_assert (g_hash_table_size (encode_session_ca->pending_encodes) == 0);
g_assert (g_hash_table_size (encode_session_ca->acquired_encode_streams) == 0);
g_assert (g_hash_table_size (encode_session_ca->bitstreams) == 0);
for (i = 0; i < N_ENCODE_STREAMS; ++i)
g_clear_pointer (&encode_session_ca->encode_streams[i], encode_stream_free);
g_clear_pointer (&encode_session_ca->surfaces, g_hash_table_unref);
G_OBJECT_CLASS (grd_encode_session_ca_sw_parent_class)->dispose (object);
}
static void
grd_encode_session_ca_sw_finalize (GObject *object)
{
GrdEncodeSessionCaSw *encode_session_ca = GRD_ENCODE_SESSION_CA_SW (object);
g_mutex_clear (&encode_session_ca->bitstreams_mutex);
g_mutex_clear (&encode_session_ca->acquired_encode_streams_mutex);
g_mutex_clear (&encode_session_ca->pending_encodes_mutex);
g_clear_pointer (&encode_session_ca->bitstreams, g_hash_table_unref);
g_clear_pointer (&encode_session_ca->acquired_encode_streams, g_hash_table_unref);
g_clear_pointer (&encode_session_ca->pending_encodes, g_hash_table_unref);
G_OBJECT_CLASS (grd_encode_session_ca_sw_parent_class)->finalize (object);
}
static void
grd_encode_session_ca_sw_init (GrdEncodeSessionCaSw *encode_session_ca)
{
encode_session_ca->pending_header = TRUE;
encode_session_ca->surfaces =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) rgb_surface_free);
encode_session_ca->pending_encodes = g_hash_table_new (NULL, NULL);
encode_session_ca->acquired_encode_streams = g_hash_table_new (NULL, NULL);
encode_session_ca->bitstreams = g_hash_table_new (NULL, NULL);
g_mutex_init (&encode_session_ca->pending_encodes_mutex);
g_mutex_init (&encode_session_ca->acquired_encode_streams_mutex);
g_mutex_init (&encode_session_ca->bitstreams_mutex);
}
static void
grd_encode_session_ca_sw_class_init (GrdEncodeSessionCaSwClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdEncodeSessionClass *encode_session_class =
GRD_ENCODE_SESSION_CLASS (klass);
object_class->dispose = grd_encode_session_ca_sw_dispose;
object_class->finalize = grd_encode_session_ca_sw_finalize;
encode_session_class->get_surface_size =
grd_encode_session_ca_sw_get_surface_size;
encode_session_class->get_image_views =
grd_encode_session_ca_sw_get_image_views;
encode_session_class->has_pending_frames =
grd_encode_session_ca_sw_has_pending_frames;
encode_session_class->encode_frame =
grd_encode_session_ca_sw_encode_frame;
encode_session_class->lock_bitstream =
grd_encode_session_ca_sw_lock_bitstream;
encode_session_class->unlock_bitstream =
grd_encode_session_ca_sw_unlock_bitstream;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include "grd-encode-session.h"
#include "grd-types.h"
#define GRD_TYPE_ENCODE_SESSION_CA_SW (grd_encode_session_ca_sw_get_type ())
G_DECLARE_FINAL_TYPE (GrdEncodeSessionCaSw, grd_encode_session_ca_sw,
GRD, ENCODE_SESSION_CA_SW, GrdEncodeSession)
GrdEncodeSessionCaSw *grd_encode_session_ca_sw_new (GrdRdpSwEncoderCa *encoder_ca,
uint32_t source_width,
uint32_t source_height,
GError **error);

1790
grd-encode-session-vaapi.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <va/va.h>
#include "grd-encode-session.h"
#define GRD_TYPE_ENCODE_SESSION_VAAPI (grd_encode_session_vaapi_get_type ())
G_DECLARE_FINAL_TYPE (GrdEncodeSessionVaapi, grd_encode_session_vaapi,
GRD, ENCODE_SESSION_VAAPI, GrdEncodeSession)
GrdEncodeSessionVaapi *grd_encode_session_vaapi_new (GrdVkDevice *vk_device,
VADisplay va_display,
gboolean supports_quality_level,
uint32_t source_width,
uint32_t source_height,
uint32_t refresh_rate,
GError **error);

244
grd-encode-session.c Normal file
View File

@@ -0,0 +1,244 @@
/*
* Copyright (C) 2024 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-encode-session.h"
typedef struct
{
GrdImageView *image_view;
GrdEncodeSessionOnBitstreamLockedFunc callback;
gpointer callback_user_data;
} GrdLockBitstreamTask;
typedef struct
{
gboolean in_shutdown;
GThread *encode_thread;
GMainContext *encode_context;
GSource *bitstream_lock_source;
GAsyncQueue *task_queue;
} GrdEncodeSessionPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GrdEncodeSession, grd_encode_session,
G_TYPE_OBJECT)
void
grd_encode_session_get_surface_size (GrdEncodeSession *encode_session,
uint32_t *surface_width,
uint32_t *surface_height)
{
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
return klass->get_surface_size (encode_session, surface_width, surface_height);
}
GList *
grd_encode_session_get_image_views (GrdEncodeSession *encode_session)
{
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
return klass->get_image_views (encode_session);
}
gboolean
grd_encode_session_has_pending_frames (GrdEncodeSession *encode_session)
{
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
return klass->has_pending_frames (encode_session);
}
gboolean
grd_encode_session_encode_frame (GrdEncodeSession *encode_session,
GrdEncodeContext *encode_context,
GrdImageView *image_view,
GError **error)
{
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
return klass->encode_frame (encode_session, encode_context, image_view, error);
}
void
grd_encode_session_lock_bitstream (GrdEncodeSession *encode_session,
GrdImageView *image_view,
GrdEncodeSessionOnBitstreamLockedFunc on_bitstream_locked,
gpointer user_data)
{
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
GrdLockBitstreamTask *task;
task = g_new0 (GrdLockBitstreamTask, 1);
task->image_view = image_view;
task->callback = on_bitstream_locked;
task->callback_user_data = user_data;
g_async_queue_push (priv->task_queue, task);
g_source_set_ready_time (priv->bitstream_lock_source, 0);
}
gboolean
grd_encode_session_unlock_bitstream (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
GError **error)
{
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
return klass->unlock_bitstream (encode_session, bitstream, error);
}
static void
stop_encode_thread (GrdEncodeSession *encode_session)
{
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
g_assert (priv->encode_context);
g_assert (priv->encode_thread);
priv->in_shutdown = TRUE;
g_main_context_wakeup (priv->encode_context);
g_clear_pointer (&priv->encode_thread, g_thread_join);
}
static void
grd_encode_session_dispose (GObject *object)
{
GrdEncodeSession *encode_session = GRD_ENCODE_SESSION (object);
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
g_assert (g_async_queue_try_pop (priv->task_queue) == NULL);
if (priv->encode_thread)
stop_encode_thread (encode_session);
if (priv->bitstream_lock_source)
{
g_source_destroy (priv->bitstream_lock_source);
g_clear_pointer (&priv->bitstream_lock_source, g_source_unref);
}
g_clear_pointer (&priv->encode_context, g_main_context_unref);
G_OBJECT_CLASS (grd_encode_session_parent_class)->dispose (object);
}
static void
grd_encode_session_finalize (GObject *object)
{
GrdEncodeSession *encode_session = GRD_ENCODE_SESSION (object);
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
g_clear_pointer (&priv->task_queue, g_async_queue_unref);
G_OBJECT_CLASS (grd_encode_session_parent_class)->finalize (object);
}
static gpointer
encode_thread_func (gpointer data)
{
GrdEncodeSession *encode_session = data;
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
while (!priv->in_shutdown)
g_main_context_iteration (priv->encode_context, TRUE);
return NULL;
}
static gboolean
lock_bitstreams (gpointer user_data)
{
GrdEncodeSession *encode_session = user_data;
GrdEncodeSessionClass *klass = GRD_ENCODE_SESSION_GET_CLASS (encode_session);
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
GrdLockBitstreamTask *task;
while ((task = g_async_queue_try_pop (priv->task_queue)))
{
GrdBitstream *bitstream;
g_autoptr (GError) error = NULL;
bitstream = klass->lock_bitstream (encode_session, task->image_view,
&error);
task->callback (encode_session, bitstream, task->callback_user_data,
error);
g_free (task);
}
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_encode_session_init (GrdEncodeSession *encode_session)
{
GrdEncodeSessionPrivate *priv =
grd_encode_session_get_instance_private (encode_session);
GSource *bitstream_lock_source;
priv->task_queue = g_async_queue_new ();
priv->encode_context = g_main_context_new ();
priv->encode_thread = g_thread_new ("Encode thread",
encode_thread_func,
encode_session);
bitstream_lock_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (bitstream_lock_source, lock_bitstreams,
encode_session, NULL);
g_source_set_ready_time (bitstream_lock_source, -1);
g_source_attach (bitstream_lock_source, priv->encode_context);
priv->bitstream_lock_source = bitstream_lock_source;
}
static void
grd_encode_session_class_init (GrdEncodeSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_encode_session_dispose;
object_class->finalize = grd_encode_session_finalize;
}

77
grd-encode-session.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_ENCODE_SESSION (grd_encode_session_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdEncodeSession, grd_encode_session,
GRD, ENCODE_SESSION, GObject)
typedef void (* GrdEncodeSessionOnBitstreamLockedFunc) (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
gpointer user_data,
GError *error);
struct _GrdEncodeSessionClass
{
GObjectClass parent_class;
void (* get_surface_size) (GrdEncodeSession *encode_session,
uint32_t *surface_width,
uint32_t *surface_height);
GList *(* get_image_views) (GrdEncodeSession *encode_session);
gboolean (* has_pending_frames) (GrdEncodeSession *encode_session);
gboolean (* encode_frame) (GrdEncodeSession *encode_session,
GrdEncodeContext *encode_context,
GrdImageView *image_view,
GError **error);
GrdBitstream *(* lock_bitstream) (GrdEncodeSession *encode_session,
GrdImageView *image_view,
GError **error);
gboolean (* unlock_bitstream) (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
GError **error);
};
void grd_encode_session_get_surface_size (GrdEncodeSession *encode_session,
uint32_t *surface_width,
uint32_t *surface_height);
GList *grd_encode_session_get_image_views (GrdEncodeSession *encode_session);
gboolean grd_encode_session_has_pending_frames (GrdEncodeSession *encode_session);
gboolean grd_encode_session_encode_frame (GrdEncodeSession *encode_session,
GrdEncodeContext *encode_context,
GrdImageView *image_view,
GError **error);
void grd_encode_session_lock_bitstream (GrdEncodeSession *encode_session,
GrdImageView *image_view,
GrdEncodeSessionOnBitstreamLockedFunc on_bitstream_locked,
gpointer user_data);
gboolean grd_encode_session_unlock_bitstream (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
GError **error);

41
grd-enum-types.c.in Normal file
View File

@@ -0,0 +1,41 @@
/*** BEGIN file-header ***/
#include <glib-object.h>
/*** END file-header ***/
/*** BEGIN file-production ***/
/* enumerations from "@filename@" */
#include "@filename@"
/*** END file-production ***/
/*** BEGIN value-header ***/
GType
@enum_name@_get_type (void)
{
static size_t g_enum_type_id = 0;
if (g_once_init_enter (&g_enum_type_id))
{
static const G@Type@Value values[] = {
/*** END value-header ***/
/*** BEGIN value-production ***/
{ @VALUENAME@, "@VALUENAME@", "@valuenick@" },
/*** END value-production ***/
/*** BEGIN value-tail ***/
{ 0, NULL, NULL }
};
GType id;
id = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
g_once_init_leave (&g_enum_type_id, id);
}
return g_enum_type_id;
}
/*** END value-tail ***/

23
grd-enum-types.h.in Normal file
View File

@@ -0,0 +1,23 @@
/*** BEGIN file-header ***/
#pragma once
#include <glib-object.h>
G_BEGIN_DECLS
/*** END file-header ***/
/*** BEGIN file-production ***/
/* enumerations from "@basename@" */
/*** END file-production ***/
/*** BEGIN file-tail ***/
G_END_DECLS
/*** END file-tail ***/
/*** BEGIN value-header ***/
GType @enum_name@_get_type (void) G_GNUC_CONST;
#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
/*** END value-header ***/

53
grd-enums.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 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.
*
*/
#pragma once
typedef enum
{
GRD_RDP_SCREEN_SHARE_MODE_MIRROR_PRIMARY,
GRD_RDP_SCREEN_SHARE_MODE_EXTEND,
} GrdRdpScreenShareMode;
typedef enum
{
GRD_VNC_AUTH_METHOD_PROMPT,
GRD_VNC_AUTH_METHOD_PASSWORD
} GrdVncAuthMethod;
typedef enum
{
GRD_VNC_SCREEN_SHARE_MODE_MIRROR_PRIMARY,
GRD_VNC_SCREEN_SHARE_MODE_EXTEND,
} GrdVncScreenShareMode;
typedef enum
{
GRD_RUNTIME_MODE_SCREEN_SHARE,
GRD_RUNTIME_MODE_HEADLESS,
GRD_RUNTIME_MODE_SYSTEM,
GRD_RUNTIME_MODE_HANDOVER,
} GrdRuntimeMode;
typedef enum
{
GRD_RDP_AUTH_METHOD_CREDENTIALS = 1 << 0,
GRD_RDP_AUTH_METHOD_KERBEROS = 1 << 1,
} GrdRdpAuthMethods;

229
grd-frame-clock.c Normal file
View File

@@ -0,0 +1,229 @@
/*
* Copyright (C) 2025 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-frame-clock.h"
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <sys/timerfd.h>
#include "grd-utils.h"
struct _GrdFrameClock
{
GObject parent;
GrdFrameClockCallback on_trigger;
gpointer on_trigger_user_data;
GSource *timer_source;
int timer_fd;
uint64_t start_time_ns;
uint64_t interval_ns;
gboolean armed;
};
G_DEFINE_TYPE (GrdFrameClock, grd_frame_clock,
G_TYPE_OBJECT)
gboolean
grd_frame_clock_is_armed (GrdFrameClock *frame_clock)
{
return frame_clock->armed;
}
static inline uint64_t
timespec_to_ns (struct timespec *spec)
{
return s2ns (spec->tv_sec) + spec->tv_nsec;
}
static uint64_t
extrapolate_next_interval_boundary (uint64_t boundary_ns,
uint64_t reference_ns,
uint64_t interval_ns)
{
uint64_t num_intervals;
num_intervals = MAX ((reference_ns - boundary_ns + interval_ns - 1) /
interval_ns, 0);
return boundary_ns + num_intervals * interval_ns;
}
static void
schedule_next_frame (GrdFrameClock *frame_clock)
{
struct itimerspec timer_spec = {};
struct timespec now = {};
uint64_t next_dispatch_time_ns;
clock_gettime (CLOCK_MONOTONIC, &now);
next_dispatch_time_ns =
extrapolate_next_interval_boundary (frame_clock->start_time_ns,
timespec_to_ns (&now),
frame_clock->interval_ns);
timer_spec.it_value.tv_sec = next_dispatch_time_ns / s2ns (1);
timer_spec.it_value.tv_nsec = next_dispatch_time_ns % s2ns (1);
timerfd_settime (frame_clock->timer_fd, TFD_TIMER_ABSTIME, &timer_spec, NULL);
}
void
grd_frame_clock_arm_timer (GrdFrameClock *frame_clock,
uint64_t clock_rate_num,
uint64_t clock_rate_denom)
{
struct timespec start_time = {};
clock_gettime (CLOCK_MONOTONIC, &start_time);
frame_clock->start_time_ns = timespec_to_ns (&start_time);
frame_clock->interval_ns = s2ns (clock_rate_denom) / clock_rate_num;
schedule_next_frame (frame_clock);
frame_clock->armed = TRUE;
}
void
grd_frame_clock_disarm_timer (GrdFrameClock *frame_clock)
{
struct itimerspec timer_spec = {};
timerfd_settime (frame_clock->timer_fd, 0, &timer_spec, NULL);
frame_clock->armed = FALSE;
}
static gboolean
timer_source_prepare (GSource *source,
int *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
timer_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_source_set_ready_time (source, -1);
return callback (user_data);
}
static GSourceFuncs timer_source_funcs =
{
.prepare = timer_source_prepare,
.dispatch = timer_source_dispatch,
};
static gboolean
handle_triggered_timer (gpointer user_data)
{
GrdFrameClock *frame_clock = user_data;
uint64_t expirations;
ssize_t ret;
ret = read (frame_clock->timer_fd, &expirations, sizeof (uint64_t));
if (ret == -1)
{
g_warning ("Failed to read from timerfd: %s", g_strerror (errno));
return G_SOURCE_CONTINUE;
}
else if (ret != sizeof (expirations))
{
g_warning ("Failed to read from timerfd: unexpected size %zi", ret);
return G_SOURCE_CONTINUE;
}
frame_clock->on_trigger (frame_clock->on_trigger_user_data);
g_assert (frame_clock->armed);
schedule_next_frame (frame_clock);
return G_SOURCE_CONTINUE;
}
GrdFrameClock *
grd_frame_clock_new (GMainContext *main_context,
GrdFrameClockCallback on_trigger,
gpointer on_trigger_user_data,
GError **error)
{
g_autoptr (GrdFrameClock) frame_clock = NULL;
frame_clock = g_object_new (GRD_TYPE_FRAME_CLOCK, NULL);
frame_clock->on_trigger = on_trigger;
frame_clock->on_trigger_user_data = on_trigger_user_data;
frame_clock->timer_fd = timerfd_create (CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (frame_clock->timer_fd == -1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create timerfd: %s", g_strerror (errno));
return NULL;
}
frame_clock->timer_source = g_source_new (&timer_source_funcs,
sizeof (GSource));
g_source_set_callback (frame_clock->timer_source, handle_triggered_timer,
frame_clock, NULL);
g_source_set_ready_time (frame_clock->timer_source, -1);
g_source_add_unix_fd (frame_clock->timer_source,
frame_clock->timer_fd, G_IO_IN);
g_source_attach (frame_clock->timer_source, main_context);
return g_steal_pointer (&frame_clock);
}
static void
grd_frame_clock_dispose (GObject *object)
{
GrdFrameClock *frame_clock = GRD_FRAME_CLOCK (object);
if (frame_clock->timer_source)
{
g_source_destroy (frame_clock->timer_source);
g_clear_pointer (&frame_clock->timer_source, g_source_unref);
}
g_clear_fd (&frame_clock->timer_fd, NULL);
G_OBJECT_CLASS (grd_frame_clock_parent_class)->dispose (object);
}
static void
grd_frame_clock_init (GrdFrameClock *frame_clock)
{
frame_clock->timer_fd = -1;
}
static void
grd_frame_clock_class_init (GrdFrameClockClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_frame_clock_dispose;
}

42
grd-frame-clock.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#define GRD_TYPE_FRAME_CLOCK (grd_frame_clock_get_type ())
G_DECLARE_FINAL_TYPE (GrdFrameClock, grd_frame_clock,
GRD, FRAME_CLOCK, GObject)
typedef void (* GrdFrameClockCallback) (gpointer user_data);
GrdFrameClock *grd_frame_clock_new (GMainContext *main_context,
GrdFrameClockCallback on_trigger,
gpointer on_trigger_user_data,
GError **error);
gboolean grd_frame_clock_is_armed (GrdFrameClock *frame_clock);
void grd_frame_clock_arm_timer (GrdFrameClock *frame_clock,
uint64_t clock_rate_num,
uint64_t clock_rate_denom);
void grd_frame_clock_disarm_timer (GrdFrameClock *frame_clock);

1017
grd-hwaccel-nvidia.c Normal file

File diff suppressed because it is too large Load Diff

102
grd-hwaccel-nvidia.h Normal file
View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 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.
*/
#pragma once
#include <ffnvcodec/dynlink_cuda.h>
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_HWACCEL_NVIDIA (grd_hwaccel_nvidia_get_type ())
G_DECLARE_FINAL_TYPE (GrdHwAccelNvidia, grd_hwaccel_nvidia,
GRD, HWACCEL_NVIDIA, GObject)
GrdHwAccelNvidia *grd_hwaccel_nvidia_new (GrdEglThread *egl_thread);
void grd_hwaccel_nvidia_get_cuda_functions (GrdHwAccelNvidia *hwaccel_nvidia,
gpointer *cuda_funcs);
void grd_hwaccel_nvidia_get_cuda_damage_kernels (GrdHwAccelNvidia *hwaccel_nvidia,
CUfunction *cu_chk_dmg_pxl,
CUfunction *cu_cmb_dmg_arr_cols,
CUfunction *cu_cmb_dmg_arr_rows,
CUfunction *cu_simplify_dmg_arr);
void grd_hwaccel_nvidia_push_cuda_context (GrdHwAccelNvidia *hwaccel_nvidia);
void grd_hwaccel_nvidia_pop_cuda_context (GrdHwAccelNvidia *hwaccel_nvidia);
gboolean grd_hwaccel_nvidia_register_read_only_gl_buffer (GrdHwAccelNvidia *hwaccel_nvidia,
CUgraphicsResource *cuda_resource,
uint32_t buffer);
void grd_hwaccel_nvidia_unregister_cuda_resource (GrdHwAccelNvidia *hwaccel_nvidia,
CUgraphicsResource cuda_resource,
CUstream cuda_stream);
gboolean grd_hwaccel_nvidia_map_cuda_resource (GrdHwAccelNvidia *hwaccel_nvidia,
CUgraphicsResource cuda_resource,
CUdeviceptr *dev_ptr,
size_t *size,
CUstream cuda_stream);
void grd_hwaccel_nvidia_unmap_cuda_resource (GrdHwAccelNvidia *hwaccel_nvidia,
CUgraphicsResource cuda_resource,
CUstream cuda_stream);
gboolean grd_hwaccel_nvidia_create_cuda_stream (GrdHwAccelNvidia *hwaccel_nvidia,
CUstream *cuda_stream);
void grd_hwaccel_nvidia_destroy_cuda_stream (GrdHwAccelNvidia *hwaccel_nvidia,
CUstream cuda_stream);
gboolean grd_hwaccel_nvidia_alloc_mem (GrdHwAccelNvidia *hwaccel_nvidia,
CUdeviceptr *device_ptr,
size_t size);
void grd_hwaccel_nvidia_clear_mem_ptr (GrdHwAccelNvidia *hwaccel_nvidia,
CUdeviceptr *device_ptr);
gboolean grd_hwaccel_nvidia_create_nvenc_session (GrdHwAccelNvidia *hwaccel_nvidia,
uint32_t *encode_session_id,
uint16_t surface_width,
uint16_t surface_height,
uint16_t *aligned_width,
uint16_t *aligned_height,
uint16_t refresh_rate);
void grd_hwaccel_nvidia_free_nvenc_session (GrdHwAccelNvidia *hwaccel_nvidia,
uint32_t encode_session_id);
gboolean grd_hwaccel_nvidia_avc420_encode_bgrx_frame (GrdHwAccelNvidia *hwaccel_nvidia,
uint32_t encode_session_id,
CUdeviceptr src_data,
CUdeviceptr *main_view_nv12,
uint16_t src_width,
uint16_t src_height,
uint16_t aligned_width,
uint16_t aligned_height,
CUstream cuda_stream);
gboolean grd_hwaccel_nvidia_avc420_retrieve_bitstream (GrdHwAccelNvidia *hwaccel_nvidia,
uint32_t encode_session_id,
uint8_t **bitstream,
uint32_t *bitstream_size);

323
grd-hwaccel-vaapi.c Normal file
View File

@@ -0,0 +1,323 @@
/*
* Copyright (C) 2022 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-hwaccel-vaapi.h"
#include <inttypes.h>
#include <fcntl.h>
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <va/va.h>
#include <va/va_drm.h>
#include "grd-encode-session-vaapi.h"
#include "grd-vk-device.h"
struct _GrdHwAccelVaapi
{
GObject parent;
GrdVkDevice *vk_device;
int drm_fd;
VADisplay va_display;
gboolean supports_quality_level;
};
G_DEFINE_TYPE (GrdHwAccelVaapi, grd_hwaccel_vaapi, G_TYPE_OBJECT)
GrdEncodeSession *
grd_hwaccel_vaapi_create_encode_session (GrdHwAccelVaapi *hwaccel_vaapi,
uint32_t source_width,
uint32_t source_height,
uint32_t refresh_rate,
GError **error)
{
return (GrdEncodeSession *) grd_encode_session_vaapi_new (hwaccel_vaapi->vk_device,
hwaccel_vaapi->va_display,
hwaccel_vaapi->supports_quality_level,
source_width, source_height,
refresh_rate, error);
}
static gboolean
initialize_vaapi (GrdHwAccelVaapi *hwaccel_vaapi,
int *version_major,
int *version_minor,
int64_t render_minor,
GError **error)
{
g_autofree char *render_node = NULL;
VAStatus va_status;
render_node = g_strdup_printf ("/dev/dri/renderD%" PRIi64, render_minor);
hwaccel_vaapi->drm_fd = g_open (render_node, O_RDWR | O_CLOEXEC, 600);
if (hwaccel_vaapi->drm_fd == -1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to open render node: %s", g_strerror (errno));
return FALSE;
}
hwaccel_vaapi->va_display = vaGetDisplayDRM (hwaccel_vaapi->drm_fd);
if (!hwaccel_vaapi->va_display)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get VA display from DRM fd");
return FALSE;
}
va_status = vaInitialize (hwaccel_vaapi->va_display,
version_major, version_minor);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to initialize VA display: %s", vaErrorStr (va_status));
return FALSE;
}
return TRUE;
}
static gboolean
has_device_avc_profile (VAProfile *profiles,
int n_profiles)
{
int i;
for (i = 0; i < n_profiles; ++i)
{
if (profiles[i] == VAProfileH264High)
return TRUE;
}
return FALSE;
}
static gboolean
has_device_avc_enc_entrypoint (VAEntrypoint *entrypoints,
int n_entrypoints)
{
int i;
for (i = 0; i < n_entrypoints; ++i)
{
if (entrypoints[i] == VAEntrypointEncSlice)
return TRUE;
}
return FALSE;
}
static gboolean
check_device_capabilities (GrdHwAccelVaapi *hwaccel_vaapi,
GError **error)
{
g_autofree VAProfile *profiles = NULL;
g_autofree VAEntrypoint *entrypoints = NULL;
VAConfigAttrib attributes[] =
{
{ .type = VAConfigAttribRTFormat },
{ .type = VAConfigAttribRateControl },
{ .type = VAConfigAttribEncMaxRefFrames },
{ .type = VAConfigAttribEncPackedHeaders },
{ .type = VAConfigAttribEncQualityRange },
};
int max_profiles;
int max_num_entrypoints;
int n_profiles = 0;
int n_entrypoints = 0;
uint32_t max_ref_l0;
VAStatus va_status;
max_profiles = vaMaxNumProfiles (hwaccel_vaapi->va_display);
if (max_profiles < 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid max num profiles: %i", max_profiles);
return FALSE;
}
profiles = g_new0 (VAProfile, max_profiles);
va_status = vaQueryConfigProfiles (hwaccel_vaapi->va_display,
profiles, &n_profiles);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to query config profiles: %s",
vaErrorStr (va_status));
return FALSE;
}
if (!has_device_avc_profile (profiles, n_profiles))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, missing required AVC profile");
return FALSE;
}
max_num_entrypoints = vaMaxNumEntrypoints (hwaccel_vaapi->va_display);
if (max_num_entrypoints < 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid max num entrypoints: %i", max_num_entrypoints);
return FALSE;
}
entrypoints = g_new0 (VAEntrypoint, max_num_entrypoints);
va_status = vaQueryConfigEntrypoints (hwaccel_vaapi->va_display,
VAProfileH264High,
entrypoints, &n_entrypoints);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to query config entrypoints: %s",
vaErrorStr (va_status));
return FALSE;
}
if (!has_device_avc_enc_entrypoint (entrypoints, n_entrypoints))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unsuitable device, missing required AVC encoding "
"entrypoint");
return FALSE;
}
va_status = vaGetConfigAttributes (hwaccel_vaapi->va_display,
VAProfileH264High, VAEntrypointEncSlice,
attributes, G_N_ELEMENTS (attributes));
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get config attributes: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (attributes[0].type == VAConfigAttribRTFormat);
if (attributes[0].value == VA_ATTRIB_NOT_SUPPORTED ||
!(attributes[0].value & VA_RT_FORMAT_YUV420))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, device does not support YUV420 format");
return FALSE;
}
g_assert (attributes[1].type == VAConfigAttribRateControl);
if (attributes[1].value == VA_ATTRIB_NOT_SUPPORTED ||
!(attributes[1].value & VA_RC_CQP))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, device does not support CQP");
return FALSE;
}
g_assert (attributes[2].type == VAConfigAttribEncMaxRefFrames);
if (attributes[2].value == VA_ATTRIB_NOT_SUPPORTED)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, invalid max ref frames attribute: %u",
attributes[2].value);
return FALSE;
}
max_ref_l0 = attributes[2].value & 0xFFFF;
if (max_ref_l0 < 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, device cannot handle reference frames");
return FALSE;
}
g_assert (attributes[3].type == VAConfigAttribEncPackedHeaders);
if (attributes[3].value == VA_ATTRIB_NOT_SUPPORTED ||
!(attributes[3].value & VA_ENC_PACKED_HEADER_SEQUENCE) ||
!(attributes[3].value & VA_ENC_PACKED_HEADER_PICTURE) ||
!(attributes[3].value & VA_ENC_PACKED_HEADER_SLICE) ||
!(attributes[3].value & VA_ENC_PACKED_HEADER_RAW_DATA))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsuitable device, device does not support required packed "
"headers (only supports 0x%08X)", attributes[3].value);
return FALSE;
}
g_assert (attributes[4].type == VAConfigAttribEncQualityRange);
hwaccel_vaapi->supports_quality_level = attributes[4].value !=
VA_ATTRIB_NOT_SUPPORTED;
return TRUE;
}
GrdHwAccelVaapi *
grd_hwaccel_vaapi_new (GrdVkDevice *vk_device,
GError **error)
{
g_autoptr (GrdHwAccelVaapi) hwaccel_vaapi = NULL;
int version_major = 0;
int version_minor = 0;
g_assert (vk_device);
hwaccel_vaapi = g_object_new (GRD_TYPE_HWACCEL_VAAPI, NULL);
hwaccel_vaapi->vk_device = vk_device;
if (!initialize_vaapi (hwaccel_vaapi, &version_major, &version_minor,
grd_vk_device_get_drm_render_node (vk_device),
error))
return NULL;
if (!check_device_capabilities (hwaccel_vaapi, error))
return NULL;
g_message ("[HWAccel.VAAPI] Successfully initialized VAAPI %i.%i with "
"vendor: %s", version_major, version_minor,
vaQueryVendorString (hwaccel_vaapi->va_display));
return g_steal_pointer (&hwaccel_vaapi);
}
static void
grd_hwaccel_vaapi_dispose (GObject *object)
{
GrdHwAccelVaapi *hwaccel_vaapi = GRD_HWACCEL_VAAPI (object);
g_clear_pointer (&hwaccel_vaapi->va_display, vaTerminate);
g_clear_fd (&hwaccel_vaapi->drm_fd, NULL);
G_OBJECT_CLASS (grd_hwaccel_vaapi_parent_class)->dispose (object);
}
static void
grd_hwaccel_vaapi_init (GrdHwAccelVaapi *hwaccel_vaapi)
{
}
static void
grd_hwaccel_vaapi_class_init (GrdHwAccelVaapiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_hwaccel_vaapi_dispose;
}

38
grd-hwaccel-vaapi.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_HWACCEL_VAAPI (grd_hwaccel_vaapi_get_type ())
G_DECLARE_FINAL_TYPE (GrdHwAccelVaapi, grd_hwaccel_vaapi,
GRD, HWACCEL_VAAPI, GObject)
GrdHwAccelVaapi *grd_hwaccel_vaapi_new (GrdVkDevice *vk_device,
GError **error);
GrdEncodeSession *grd_hwaccel_vaapi_create_encode_session (GrdHwAccelVaapi *hwaccel_vaapi,
uint32_t source_width,
uint32_t source_height,
uint32_t refresh_rate,
GError **error);

940
grd-hwaccel-vulkan.c Normal file
View File

@@ -0,0 +1,940 @@
/*
* Copyright (C) 2022 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-hwaccel-vulkan.h"
#include <drm_fourcc.h>
#include <gio/gio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include "grd-debug.h"
#include "grd-egl-thread.h"
#include "grd-vk-device.h"
#include "grd-vk-physical-device.h"
#include "grd-vk-utils.h"
#define VULKAN_API_VERSION VK_API_VERSION_1_2
struct _GrdHwAccelVulkan
{
GObject parent;
GrdEglThread *egl_thread;
VkInstance vk_instance;
/* VK_EXT_debug_utils */
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
VkDebugUtilsMessengerEXT vk_debug_messenger;
GrdVkSPIRVSources spirv_sources;
};
G_DEFINE_TYPE (GrdHwAccelVulkan, grd_hwaccel_vulkan, G_TYPE_OBJECT)
static gboolean
list_contains_drm_format_modifier (uint64_t *modifiers,
int n_modifiers,
uint64_t modifier)
{
int i;
for (i = 0; i < n_modifiers; ++i)
{
if (modifiers[i] == modifier)
return TRUE;
}
return FALSE;
}
static GArray *
get_egl_vulkan_format_modifier_intersection (GrdHwAccelVulkan *hwaccel_vulkan,
VkPhysicalDevice vk_physical_device,
uint32_t drm_format,
uint32_t *required_n_planes,
VkFormatFeatureFlags2 *required_features,
GError **error)
{
GrdEglThread *egl_thread = hwaccel_vulkan->egl_thread;
VkFormatProperties2 format_properties_2 = {};
VkDrmFormatModifierPropertiesList2EXT modifier_properties_list2 = {};
g_autofree VkDrmFormatModifierProperties2EXT *modifier_properties_2 = NULL;
VkFormat vk_format = VK_FORMAT_UNDEFINED;
g_autofree uint64_t *egl_modifiers = NULL;
int n_egl_modifiers;
GArray *vk_modifiers;
uint32_t i;
if (!grd_vk_get_vk_format_from_drm_format (drm_format, &vk_format, error))
return NULL;
format_properties_2.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
modifier_properties_list2.sType =
VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT;
grd_vk_append_to_chain (&format_properties_2, &modifier_properties_list2);
vkGetPhysicalDeviceFormatProperties2 (vk_physical_device, vk_format,
&format_properties_2);
if (modifier_properties_list2.drmFormatModifierCount == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"No DRM format modifiers available for DRM format %u",
drm_format);
return NULL;
}
modifier_properties_2 =
g_new0 (VkDrmFormatModifierProperties2EXT,
modifier_properties_list2.drmFormatModifierCount);
modifier_properties_list2.pDrmFormatModifierProperties = modifier_properties_2;
vkGetPhysicalDeviceFormatProperties2 (vk_physical_device, vk_format,
&format_properties_2);
if (!grd_egl_thread_get_modifiers_for_format (egl_thread, drm_format,
&n_egl_modifiers,
&egl_modifiers))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"No DRM format modifiers available for DRM format %u from "
"EGL", drm_format);
return NULL;
}
vk_modifiers = g_array_new (FALSE, FALSE, sizeof (uint64_t));
for (i = 0; i < modifier_properties_list2.drmFormatModifierCount; i++)
{
uint64_t modifier = modifier_properties_2[i].drmFormatModifier;
uint32_t n_planes = modifier_properties_2[i].drmFormatModifierPlaneCount;
VkFormatFeatureFlags2 tiling_features =
modifier_properties_2[i].drmFormatModifierTilingFeatures;
if (required_n_planes &&
n_planes != *required_n_planes)
continue;
if (required_features &&
(tiling_features & *required_features) != *required_features)
continue;
if (!list_contains_drm_format_modifier (egl_modifiers, n_egl_modifiers,
modifier))
continue;
g_array_append_vals (vk_modifiers, &modifier, 1);
}
if (vk_modifiers->len == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"No common DRM format modifiers available for DRM format %u "
"from EGL and Vulkan", drm_format);
g_array_free (vk_modifiers, TRUE);
return NULL;
}
return vk_modifiers;
}
gboolean
grd_hwaccel_vulkan_get_modifiers_for_format (GrdHwAccelVulkan *hwaccel_vulkan,
GrdVkPhysicalDevice *physical_device,
uint32_t drm_format,
int *out_n_modifiers,
uint64_t **out_modifiers)
{
VkPhysicalDevice vk_physical_device =
grd_vk_physical_device_get_physical_device (physical_device);
GArray *modifiers;
g_autoptr (GError) error = NULL;
modifiers =
get_egl_vulkan_format_modifier_intersection (hwaccel_vulkan,
vk_physical_device,
drm_format,
NULL, NULL, &error);
if (!modifiers)
return FALSE;
*out_n_modifiers = modifiers->len;
*out_modifiers = (uint64_t *) g_array_free (modifiers, FALSE);
return TRUE;
}
static gboolean
has_vk_extension (VkExtensionProperties *properties,
uint32_t n_properties,
const char *extension)
{
uint32_t i;
for (i = 0; i < n_properties; ++i)
{
if (strcmp (properties[i].extensionName, extension) == 0)
return TRUE;
}
return FALSE;
}
static gboolean
check_device_extension (VkExtensionProperties *properties,
uint32_t n_properties,
const char *extension)
{
if (!has_vk_extension (properties, n_properties, extension))
{
g_debug ("[HWAccel.Vulkan] Skipping device. Missing extension '%s'",
extension);
return FALSE;
}
return TRUE;
}
static gboolean
check_device_extensions (VkPhysicalDevice vk_physical_device)
{
g_autofree VkExtensionProperties *properties = NULL;
uint32_t n_properties = 0;
VkResult vk_result;
vk_result = vkEnumerateDeviceExtensionProperties (vk_physical_device, NULL,
&n_properties, NULL);
if (vk_result != VK_SUCCESS)
{
g_warning ("[HWAccel.Vulkan] Failed to enumerate device extension "
"properties: %i", vk_result);
return FALSE;
}
if (n_properties == 0)
return TRUE;
properties = g_new0 (VkExtensionProperties, n_properties);
vk_result = vkEnumerateDeviceExtensionProperties (vk_physical_device, NULL,
&n_properties, properties);
if (vk_result != VK_SUCCESS)
{
g_warning ("[HWAccel.Vulkan] Failed to enumerate device extension "
"properties: %i", vk_result);
return FALSE;
}
if (!check_device_extension (properties, n_properties,
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_KHR_ZERO_INITIALIZE_WORKGROUP_MEMORY_EXTENSION_NAME))
return FALSE;
if (!check_device_extension (properties, n_properties,
VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME))
return FALSE;
return TRUE;
}
static gboolean
get_queue_family_properties (VkPhysicalDevice vk_physical_device,
VkQueueFamilyProperties2 **properties_2,
uint32_t *n_properties_2)
{
uint32_t i;
*properties_2 = NULL;
*n_properties_2 = 0;
vkGetPhysicalDeviceQueueFamilyProperties2 (vk_physical_device,
n_properties_2, NULL);
if (*n_properties_2 == 0)
return FALSE;
*properties_2 = g_new0 (VkQueueFamilyProperties2, *n_properties_2);
for (i = 0; i < *n_properties_2; ++i)
(*properties_2)[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
vkGetPhysicalDeviceQueueFamilyProperties2 (vk_physical_device,
n_properties_2, *properties_2);
return TRUE;
}
static gboolean
has_queue_family_with_bitmask (VkPhysicalDevice vk_physical_device,
VkQueueFlags bitmask)
{
g_autofree VkQueueFamilyProperties2 *properties_2 = NULL;
uint32_t n_properties_2 = 0;
uint32_t i;
if (!get_queue_family_properties (vk_physical_device, &properties_2,
&n_properties_2))
return FALSE;
for (i = 0; i < n_properties_2; ++i)
{
if (properties_2[i].queueFamilyProperties.queueFlags & bitmask)
return TRUE;
}
return FALSE;
}
static gboolean
supports_bgrx_dma_buf_images (GrdHwAccelVulkan *hwaccel_vulkan,
VkPhysicalDevice vk_physical_device,
GError **error)
{
uint32_t drm_format;
uint32_t required_n_planes;
VkFormatFeatureFlags2 required_features;
GArray *modifier_array;
uint64_t *modifiers;
uint32_t i;
drm_format = DRM_FORMAT_XRGB8888;
required_n_planes = 1;
required_features = VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT;
modifier_array =
get_egl_vulkan_format_modifier_intersection (hwaccel_vulkan,
vk_physical_device,
drm_format,
&required_n_planes,
&required_features,
error);
if (!modifier_array)
return FALSE;
modifiers = (uint64_t *) modifier_array->data;
for (i = 0; i < modifier_array->len; ++i)
{
g_debug ("[HWAccel.Vulkan] Found DRM format modifier %lu for DRM "
"format %u", modifiers[i], drm_format);
}
g_array_free (modifier_array, TRUE);
return TRUE;
}
static gboolean
check_physical_device (GrdHwAccelVulkan *hwaccel_vulkan,
VkPhysicalDevice vk_physical_device,
int64_t render_major_egl,
int64_t render_minor_egl,
GrdVkDeviceFeatures *device_features)
{
VkPhysicalDeviceProperties properties = {};
VkPhysicalDeviceProperties2 properties_2 = {};
VkPhysicalDeviceDrmPropertiesEXT drm_properties = {};
VkPhysicalDeviceFeatures2 features_2 = {};
VkPhysicalDeviceVulkan12Features vulkan12_features = {};
VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeatures zero_init_features = {};
g_autoptr (GError) error = NULL;
*device_features = 0;
vkGetPhysicalDeviceProperties (vk_physical_device, &properties);
if (properties.apiVersion < VULKAN_API_VERSION)
{
g_debug ("[HWAccel.Vulkan] Skipping device. API version too old "
"(have %u, need %u)", properties.apiVersion, VULKAN_API_VERSION);
return FALSE;
}
if (!properties.limits.timestampComputeAndGraphics)
{
g_debug ("[HWAccel.Vulkan] Skipping device. Support for "
"'timestampComputeAndGraphics' is missing");
return FALSE;
}
if (!check_device_extensions (vk_physical_device))
return FALSE;
properties_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
drm_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT;
grd_vk_append_to_chain (&properties_2, &drm_properties);
vkGetPhysicalDeviceProperties2 (vk_physical_device, &properties_2);
if (drm_properties.hasRender != VK_TRUE)
{
g_debug ("[HWAccel.Vulkan] Skipping device. Device has no "
"DRM render node");
return FALSE;
}
if (drm_properties.renderMajor != render_major_egl ||
drm_properties.renderMinor != render_minor_egl)
{
g_debug ("[HWAccel.Vulkan] Skipping device. Vulkan and EGL device DRM "
"render nodes don't match");
return FALSE;
}
features_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
vulkan12_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
zero_init_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES;
grd_vk_append_to_chain (&features_2, &vulkan12_features);
grd_vk_append_to_chain (&features_2, &zero_init_features);
vkGetPhysicalDeviceFeatures2 (vk_physical_device, &features_2);
if (zero_init_features.shaderZeroInitializeWorkgroupMemory != VK_TRUE)
{
g_debug ("[HWAccel.Vulkan] Skipping device. Support for "
"'shaderZeroInitializeWorkgroupMemory' is missing");
return FALSE;
}
if (vulkan12_features.descriptorBindingSampledImageUpdateAfterBind != VK_FALSE)
*device_features |= GRD_VK_DEVICE_FEATURE_UPDATE_AFTER_BIND_SAMPLED_IMAGE;
if (vulkan12_features.descriptorBindingStorageImageUpdateAfterBind != VK_FALSE)
*device_features |= GRD_VK_DEVICE_FEATURE_UPDATE_AFTER_BIND_STORAGE_IMAGE;
if (!has_queue_family_with_bitmask (vk_physical_device,
VK_QUEUE_COMPUTE_BIT |
VK_QUEUE_TRANSFER_BIT))
{
g_debug ("[HWAccel.Vulkan] Skipping device. Missing device queue family "
"with (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT) bitmask");
return FALSE;
}
if (!supports_bgrx_dma_buf_images (hwaccel_vulkan, vk_physical_device,
&error))
{
g_debug ("[HWAccel.Vulkan] Skipping device: %s", error->message);
return FALSE;
}
return TRUE;
}
static GrdVkPhysicalDevice *
find_and_create_physical_device (GrdHwAccelVulkan *hwaccel_vulkan,
const VkPhysicalDevice *physical_devices,
uint32_t n_physical_devices,
int64_t render_major,
int64_t render_minor,
GError **error)
{
uint32_t i;
for (i = 0; i < n_physical_devices; ++i)
{
VkPhysicalDevice vk_physical_device = physical_devices[i];
GrdVkDeviceFeatures device_features = 0;
g_debug ("[HWAccel.Vulkan] Checking physical device %u/%u",
i + 1, n_physical_devices);
if (check_physical_device (hwaccel_vulkan, vk_physical_device,
render_major, render_minor, &device_features))
return grd_vk_physical_device_new (vk_physical_device, device_features);
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not find proper device");
return NULL;
}
GrdVkPhysicalDevice *
grd_hwaccel_vulkan_acquire_physical_device (GrdHwAccelVulkan *hwaccel_vulkan,
GError **error)
{
GrdEglThread *egl_thread = hwaccel_vulkan->egl_thread;
g_autofree VkPhysicalDevice *physical_devices = NULL;
uint32_t n_physical_devices = 0;
const char *drm_render_node;
struct stat stat_buf = {};
VkResult vk_result;
int ret;
g_assert (egl_thread);
vk_result = vkEnumeratePhysicalDevices (hwaccel_vulkan->vk_instance,
&n_physical_devices, NULL);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate physical devices: %i", vk_result);
return NULL;
}
if (n_physical_devices == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No physical devices found: %i", vk_result);
return NULL;
}
physical_devices = g_new0 (VkPhysicalDevice, n_physical_devices);
vk_result = vkEnumeratePhysicalDevices (hwaccel_vulkan->vk_instance,
&n_physical_devices,
physical_devices);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate physical devices: %i", vk_result);
return NULL;
}
drm_render_node = grd_egl_thread_get_drm_render_node (egl_thread);
ret = stat (drm_render_node, &stat_buf);
if (ret < 0)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
"Failed to check status of DRM render node: %s",
strerror (-ret));
return NULL;
}
if (!S_ISCHR (stat_buf.st_mode))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid file mode for DRM render node");
return NULL;
}
return find_and_create_physical_device (hwaccel_vulkan,
physical_devices, n_physical_devices,
major (stat_buf.st_rdev),
minor (stat_buf.st_rdev),
error);
}
GrdVkDevice *
grd_hwaccel_vulkan_acquire_device (GrdHwAccelVulkan *hwaccel_vulkan,
GrdVkPhysicalDevice *physical_device,
GError **error)
{
return grd_vk_device_new (physical_device,
&hwaccel_vulkan->spirv_sources,
error);
}
static gboolean
has_vk_layer (VkLayerProperties *properties,
uint32_t n_properties,
const char *extension)
{
uint32_t i;
for (i = 0; i < n_properties; ++i)
{
if (strcmp (properties[i].layerName, extension) == 0)
return TRUE;
}
return FALSE;
}
static gboolean
check_instance_layers (GrdHwAccelVulkan *hwaccel_vulkan,
gboolean *has_validation_layer,
GError **error)
{
g_autofree VkLayerProperties *properties = NULL;
uint32_t n_properties = 0;
VkResult vk_result;
*has_validation_layer = FALSE;
vk_result = vkEnumerateInstanceLayerProperties (&n_properties, NULL);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate instance layer properties: %i",
vk_result);
return FALSE;
}
if (n_properties == 0)
return TRUE;
properties = g_new0 (VkLayerProperties, n_properties);
vk_result = vkEnumerateInstanceLayerProperties (&n_properties, properties);
if (vk_result == VK_INCOMPLETE)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Instance layer properties changed during count fetch");
return FALSE;
}
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate instance layer properties: %i",
vk_result);
return FALSE;
}
*has_validation_layer = has_vk_layer (properties, n_properties,
"VK_LAYER_KHRONOS_validation");
return TRUE;
}
static gboolean
check_instance_extensions (GrdHwAccelVulkan *hwaccel_vulkan,
gboolean *supports_debug_utils,
GError **error)
{
g_autofree VkExtensionProperties *properties = NULL;
uint32_t n_properties = 0;
VkResult vk_result;
*supports_debug_utils = FALSE;
vk_result = vkEnumerateInstanceExtensionProperties (NULL,
&n_properties,
NULL);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate instance extension properties: %i",
vk_result);
return FALSE;
}
if (n_properties == 0)
return TRUE;
properties = g_new0 (VkExtensionProperties, n_properties);
vk_result = vkEnumerateInstanceExtensionProperties (NULL,
&n_properties,
properties);
if (vk_result == VK_INCOMPLETE)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Instance extension properties changed during count fetch");
return FALSE;
}
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to enumerate instance extension properties: %i",
vk_result);
return FALSE;
}
*supports_debug_utils = has_vk_extension (properties, n_properties,
VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
return TRUE;
}
static gboolean
load_instance_debug_funcs (GrdHwAccelVulkan *hwaccel_vulkan,
GError **error)
{
VkInstance vk_instance = hwaccel_vulkan->vk_instance;
hwaccel_vulkan->vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)
vkGetInstanceProcAddr (vk_instance, "vkCreateDebugUtilsMessengerEXT");
if (!hwaccel_vulkan->vkCreateDebugUtilsMessengerEXT)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get instance function address for function "
"\"vkCreateDebugUtilsMessengerEXT\"");
return FALSE;
}
hwaccel_vulkan->vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)
vkGetInstanceProcAddr (vk_instance, "vkDestroyDebugUtilsMessengerEXT");
if (!hwaccel_vulkan->vkDestroyDebugUtilsMessengerEXT)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get instance function address for function "
"\"vkDestroyDebugUtilsMessengerEXT\"");
return FALSE;
}
return TRUE;
}
static const char *
message_severity_to_string (VkDebugUtilsMessageSeverityFlagBitsEXT message_severity)
{
switch (message_severity)
{
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
return "Verbose";
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
return "Info";
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
return "Warning";
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
return "Error";
default:
g_assert_not_reached ();
return NULL;
}
}
static VkBool32
debug_messenger (VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type_flags,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
void* user_data)
{
g_debug ("[HWAccel.Vulkan] Debug[%s]: Types: 0x%08X: Id: [%i: %s]: %s",
message_severity_to_string (message_severity), message_type_flags,
callback_data->messageIdNumber, callback_data->pMessageIdName,
callback_data->pMessage);
return VK_FALSE;
}
static gboolean
create_debug_messenger (GrdHwAccelVulkan *hwaccel_vulkan,
GError **error)
{
VkInstance vk_instance = hwaccel_vulkan->vk_instance;
VkDebugUtilsMessengerCreateInfoEXT create_info = {};
VkDebugUtilsMessengerEXT vk_debug_messenger = VK_NULL_HANDLE;
VkResult vk_result;
g_assert (vk_instance != VK_NULL_HANDLE);
g_assert (hwaccel_vulkan->vkCreateDebugUtilsMessengerEXT);
create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
create_info.pfnUserCallback = debug_messenger;
create_info.pUserData = NULL;
vk_result =
hwaccel_vulkan->vkCreateDebugUtilsMessengerEXT (vk_instance,
&create_info, NULL,
&vk_debug_messenger);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create debug messenger: %i", vk_result);
return FALSE;
}
g_assert (vk_debug_messenger != VK_NULL_HANDLE);
hwaccel_vulkan->vk_debug_messenger = vk_debug_messenger;
return TRUE;
}
static gboolean
create_vk_instance (GrdHwAccelVulkan *hwaccel_vulkan,
GError **error)
{
VkInstanceCreateInfo instance_create_info = {};
VkApplicationInfo app_info = {};
gboolean has_validation_layer = FALSE;
gboolean supports_debug_utils = FALSE;
gboolean vulkan_debug = FALSE;
const char *layers[1] = {};
uint32_t n_layers = 0;
const char *extensions[1] = {};
uint32_t n_extensions = 0;
VkResult vk_result;
if (grd_get_debug_flags () & GRD_DEBUG_VK_VALIDATION)
vulkan_debug = TRUE;
if (!check_instance_layers (hwaccel_vulkan, &has_validation_layer, error))
return FALSE;
if (!check_instance_extensions (hwaccel_vulkan, &supports_debug_utils, error))
return FALSE;
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.apiVersion = VULKAN_API_VERSION;
if (vulkan_debug)
{
if (has_validation_layer)
layers[n_layers++] = "VK_LAYER_KHRONOS_validation";
else
g_warning ("[HWAccel.Vulkan] VK_LAYER_KHRONOS_validation is unavailable");
if (supports_debug_utils)
extensions[n_extensions++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
else
g_warning ("[HWAccel.Vulkan] VK_EXT_debug_utils is unavailable");
}
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pApplicationInfo = &app_info;
instance_create_info.enabledLayerCount = n_layers;
instance_create_info.ppEnabledLayerNames = layers;
instance_create_info.enabledExtensionCount = n_extensions;
instance_create_info.ppEnabledExtensionNames = extensions;
vk_result = vkCreateInstance (&instance_create_info, NULL,
&hwaccel_vulkan->vk_instance);
if (vk_result != VK_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create instance: %i", vk_result);
return FALSE;
}
g_assert (hwaccel_vulkan->vk_instance != VK_NULL_HANDLE);
if (!vulkan_debug || !supports_debug_utils)
return TRUE;
if (!load_instance_debug_funcs (hwaccel_vulkan, error))
return FALSE;
if (!create_debug_messenger (hwaccel_vulkan, error))
return FALSE;
return TRUE;
}
GrdHwAccelVulkan *
grd_hwaccel_vulkan_new (GrdEglThread *egl_thread,
GError **error)
{
g_autoptr (GrdHwAccelVulkan) hwaccel_vulkan = NULL;
hwaccel_vulkan = g_object_new (GRD_TYPE_HWACCEL_VULKAN, NULL);
hwaccel_vulkan->egl_thread = egl_thread;
if (!create_vk_instance (hwaccel_vulkan, error))
return NULL;
return g_steal_pointer (&hwaccel_vulkan);
}
static void
spirv_source_free (GrdVkSPIRVSource *spirv_source)
{
g_free (spirv_source->data);
g_free (spirv_source);
}
static void
free_spirv_sources (GrdHwAccelVulkan *hwaccel_vulkan)
{
GrdVkSPIRVSources *spirv_sources = &hwaccel_vulkan->spirv_sources;
g_clear_pointer (&spirv_sources->avc_dual_view, spirv_source_free);
}
static void
grd_hwaccel_vulkan_dispose (GObject *object)
{
GrdHwAccelVulkan *hwaccel_vulkan = GRD_HWACCEL_VULKAN (object);
free_spirv_sources (hwaccel_vulkan);
if (hwaccel_vulkan->vk_debug_messenger != VK_NULL_HANDLE)
{
VkInstance vk_instance = hwaccel_vulkan->vk_instance;
VkDebugUtilsMessengerEXT vk_debug_messenger =
hwaccel_vulkan->vk_debug_messenger;
g_assert (vk_instance != VK_NULL_HANDLE);
g_assert (hwaccel_vulkan->vkDestroyDebugUtilsMessengerEXT);
hwaccel_vulkan->vkDestroyDebugUtilsMessengerEXT (vk_instance,
vk_debug_messenger,
NULL);
hwaccel_vulkan->vk_debug_messenger = VK_NULL_HANDLE;
}
if (hwaccel_vulkan->vk_instance != VK_NULL_HANDLE)
{
vkDestroyInstance (hwaccel_vulkan->vk_instance, NULL);
hwaccel_vulkan->vk_instance = VK_NULL_HANDLE;
}
G_OBJECT_CLASS (grd_hwaccel_vulkan_parent_class)->dispose (object);
}
static gboolean
load_spirv_source (const char *path,
GrdVkSPIRVSource **spirv_source,
GError **error)
{
char *data = NULL;
size_t size = 0;
if (!g_file_get_contents (path, &data, &size, error))
return FALSE;
/* SPIR-V sources are always aligned to 32 bits */
g_assert (size % 4 == 0);
*spirv_source = g_new0 (GrdVkSPIRVSource, 1);
(*spirv_source)->data = data;
(*spirv_source)->size = size;
return TRUE;
}
static void
load_spirv_sources (GrdHwAccelVulkan *hwaccel_vulkan)
{
GrdVkSPIRVSources *spirv_sources = &hwaccel_vulkan->spirv_sources;
g_autofree char *avc_dual_view_path = NULL;
g_autoptr (GError) error = NULL;
avc_dual_view_path = g_strdup_printf ("%s/grd-avc-dual-view_opt.spv",
GRD_SHADER_DIR);
if (!load_spirv_source (avc_dual_view_path, &spirv_sources->avc_dual_view,
&error))
g_error ("[HWAccel.Vulkan] Failed to load shader: %s", error->message);
}
static void
grd_hwaccel_vulkan_init (GrdHwAccelVulkan *hwaccel_vulkan)
{
load_spirv_sources (hwaccel_vulkan);
}
static void
grd_hwaccel_vulkan_class_init (GrdHwAccelVulkanClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_hwaccel_vulkan_dispose;
}

56
grd-hwaccel-vulkan.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2022 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_HWACCEL_VULKAN (grd_hwaccel_vulkan_get_type ())
G_DECLARE_FINAL_TYPE (GrdHwAccelVulkan, grd_hwaccel_vulkan,
GRD, HWACCEL_VULKAN, GObject)
struct _GrdVkSPIRVSource
{
char *data;
size_t size;
};
struct _GrdVkSPIRVSources
{
GrdVkSPIRVSource *avc_dual_view;
};
GrdHwAccelVulkan *grd_hwaccel_vulkan_new (GrdEglThread *egl_thread,
GError **error);
gboolean grd_hwaccel_vulkan_get_modifiers_for_format (GrdHwAccelVulkan *hwaccel_vulkan,
GrdVkPhysicalDevice *physical_device,
uint32_t drm_format,
int *out_n_modifiers,
uint64_t **out_modifiers);
GrdVkPhysicalDevice *grd_hwaccel_vulkan_acquire_physical_device (GrdHwAccelVulkan *hwaccel_vulkan,
GError **error);
GrdVkDevice *grd_hwaccel_vulkan_acquire_device (GrdHwAccelVulkan *hwaccel_vulkan,
GrdVkPhysicalDevice *physical_device,
GError **error);

123
grd-image-view-nv12.c Normal file
View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2024 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-image-view-nv12.h"
#include "grd-vk-image.h"
struct _GrdImageViewNV12
{
GrdImageView parent;
GrdVkImage *vk_y_layer;
GrdVkImage *vk_uv_layer;
};
G_DEFINE_TYPE (GrdImageViewNV12, grd_image_view_nv12,
GRD_TYPE_IMAGE_VIEW)
VkImageView
grd_image_view_nv12_get_y_layer (GrdImageViewNV12 *image_view_nv12)
{
return grd_vk_image_get_image_view (image_view_nv12->vk_y_layer);
}
VkImageView
grd_image_view_nv12_get_uv_layer (GrdImageViewNV12 *image_view_nv12)
{
return grd_vk_image_get_image_view (image_view_nv12->vk_uv_layer);
}
GList *
grd_image_view_nv12_get_images (GrdImageViewNV12 *image_view_nv12)
{
GList *images = NULL;
if (image_view_nv12->vk_y_layer)
images = g_list_append (images, image_view_nv12->vk_y_layer);
if (image_view_nv12->vk_uv_layer)
images = g_list_append (images, image_view_nv12->vk_uv_layer);
return images;
}
VkImageLayout
grd_image_view_nv12_get_image_layout (GrdImageViewNV12 *image_view_nv12)
{
g_assert (grd_vk_image_get_image_layout (image_view_nv12->vk_y_layer) ==
grd_vk_image_get_image_layout (image_view_nv12->vk_uv_layer));
return grd_vk_image_get_image_layout (image_view_nv12->vk_y_layer);
}
void
grd_image_view_nv12_set_image_layout (GrdImageViewNV12 *image_view_nv12,
VkImageLayout vk_image_layout)
{
grd_vk_image_set_image_layout (image_view_nv12->vk_y_layer, vk_image_layout);
grd_vk_image_set_image_layout (image_view_nv12->vk_uv_layer, vk_image_layout);
}
void
grd_image_view_nv12_notify_image_view_release (GrdImageView *image_view)
{
}
GrdImageViewNV12 *
grd_image_view_nv12_new (GrdVkImage *vk_y_layer,
GrdVkImage *vk_uv_layer)
{
GrdImageViewNV12 *image_view_nv12;
image_view_nv12 = g_object_new (GRD_TYPE_IMAGE_VIEW_NV12, NULL);
image_view_nv12->vk_y_layer = vk_y_layer;
image_view_nv12->vk_uv_layer = vk_uv_layer;
return image_view_nv12;
}
static void
grd_image_view_nv12_dispose (GObject *object)
{
GrdImageViewNV12 *image_view_nv12 = GRD_IMAGE_VIEW_NV12 (object);
g_clear_object (&image_view_nv12->vk_uv_layer);
g_clear_object (&image_view_nv12->vk_y_layer);
G_OBJECT_CLASS (grd_image_view_nv12_parent_class)->dispose (object);
}
static void
grd_image_view_nv12_init (GrdImageViewNV12 *image_view_nv12)
{
}
static void
grd_image_view_nv12_class_init (GrdImageViewNV12Class *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdImageViewClass *image_view_class = GRD_IMAGE_VIEW_CLASS (klass);
object_class->dispose = grd_image_view_nv12_dispose;
image_view_class->notify_image_view_release =
grd_image_view_nv12_notify_image_view_release;
}

43
grd-image-view-nv12.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <vulkan/vulkan.h>
#include "grd-image-view.h"
#include "grd-types.h"
#define GRD_TYPE_IMAGE_VIEW_NV12 (grd_image_view_nv12_get_type ())
G_DECLARE_FINAL_TYPE (GrdImageViewNV12, grd_image_view_nv12,
GRD, IMAGE_VIEW_NV12, GrdImageView)
GrdImageViewNV12 *grd_image_view_nv12_new (GrdVkImage *vk_y_layer,
GrdVkImage *vk_uv_layer);
VkImageView grd_image_view_nv12_get_y_layer (GrdImageViewNV12 *image_view_nv12);
VkImageView grd_image_view_nv12_get_uv_layer (GrdImageViewNV12 *image_view_nv12);
GList *grd_image_view_nv12_get_images (GrdImageViewNV12 *image_view_nv12);
VkImageLayout grd_image_view_nv12_get_image_layout (GrdImageViewNV12 *image_view_nv12);
void grd_image_view_nv12_set_image_layout (GrdImageViewNV12 *image_view_nv12,
VkImageLayout vk_image_layout);

89
grd-image-view-rgb.c Normal file
View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2024 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-image-view-rgb.h"
struct _GrdImageViewRGB
{
GrdImageView parent;
GrdLocalBuffer *local_buffer;
GrdImageViewRGBReleaseCallback on_release;
gpointer on_release_user_data;
};
G_DEFINE_TYPE (GrdImageViewRGB, grd_image_view_rgb,
GRD_TYPE_IMAGE_VIEW)
GrdLocalBuffer *
grd_image_view_rgb_get_local_buffer (GrdImageViewRGB *image_view_rgb)
{
return image_view_rgb->local_buffer;
}
void
grd_image_view_rgb_attach_local_buffer (GrdImageViewRGB *image_view_rgb,
GrdLocalBuffer *local_buffer,
GrdImageViewRGBReleaseCallback callback,
gpointer user_data)
{
g_assert (!image_view_rgb->local_buffer);
image_view_rgb->local_buffer = local_buffer;
image_view_rgb->on_release = callback;
image_view_rgb->on_release_user_data = user_data;
}
static void
grd_image_view_rgb_notify_image_view_release (GrdImageView *image_view)
{
GrdImageViewRGB *image_view_rgb = GRD_IMAGE_VIEW_RGB (image_view);
if (!image_view_rgb->on_release)
return;
image_view_rgb->on_release (image_view_rgb->on_release_user_data,
image_view_rgb->local_buffer);
image_view_rgb->local_buffer = NULL;
image_view_rgb->on_release = NULL;
image_view_rgb->on_release_user_data = NULL;
}
GrdImageViewRGB *
grd_image_view_rgb_new (void)
{
return g_object_new (GRD_TYPE_IMAGE_VIEW_RGB, NULL);
}
static void
grd_image_view_rgb_init (GrdImageViewRGB *image_view_rgb)
{
}
static void
grd_image_view_rgb_class_init (GrdImageViewRGBClass *klass)
{
GrdImageViewClass *image_view_class = GRD_IMAGE_VIEW_CLASS (klass);
image_view_class->notify_image_view_release =
grd_image_view_rgb_notify_image_view_release;
}

39
grd-image-view-rgb.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include "grd-image-view.h"
#include "grd-types.h"
#define GRD_TYPE_IMAGE_VIEW_RGB (grd_image_view_rgb_get_type ())
G_DECLARE_FINAL_TYPE (GrdImageViewRGB, grd_image_view_rgb,
GRD, IMAGE_VIEW_RGB, GrdImageView)
typedef void (* GrdImageViewRGBReleaseCallback) (gpointer user_data,
GrdLocalBuffer *local_buffer);
GrdImageViewRGB *grd_image_view_rgb_new (void);
GrdLocalBuffer *grd_image_view_rgb_get_local_buffer (GrdImageViewRGB *image_view_rgb);
void grd_image_view_rgb_attach_local_buffer (GrdImageViewRGB *image_view_rgb,
GrdLocalBuffer *local_buffer,
GrdImageViewRGBReleaseCallback callback,
gpointer user_data);

43
grd-image-view.c Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 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-image-view.h"
G_DEFINE_ABSTRACT_TYPE (GrdImageView, grd_image_view,
G_TYPE_OBJECT)
void
grd_image_view_notify_image_view_release (GrdImageView *image_view)
{
GrdImageViewClass *klass = GRD_IMAGE_VIEW_GET_CLASS (image_view);
klass->notify_image_view_release (image_view);
}
static void
grd_image_view_init (GrdImageView *image_view)
{
}
static void
grd_image_view_class_init (GrdImageViewClass *klass)
{
}

35
grd-image-view.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <glib-object.h>
#define GRD_TYPE_IMAGE_VIEW (grd_image_view_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdImageView, grd_image_view,
GRD, IMAGE_VIEW, GObject)
struct _GrdImageViewClass
{
GObjectClass parent_class;
void (* notify_image_view_release) (GrdImageView *image_view);
};
void grd_image_view_notify_image_view_release (GrdImageView *image_view);

97
grd-local-buffer-copy.c Normal file
View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2025 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-local-buffer-copy.h"
struct _GrdLocalBufferCopy
{
GrdLocalBuffer parent;
uint8_t *buffer;
uint32_t buffer_stride;
};
G_DEFINE_TYPE (GrdLocalBufferCopy, grd_local_buffer_copy,
GRD_TYPE_LOCAL_BUFFER)
static uint8_t *
grd_local_buffer_copy_get_buffer (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferCopy *local_buffer_copy = GRD_LOCAL_BUFFER_COPY (local_buffer);
return local_buffer_copy->buffer;
}
static uint32_t
grd_local_buffer_copy_get_buffer_stride (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferCopy *local_buffer_copy = GRD_LOCAL_BUFFER_COPY (local_buffer);
return local_buffer_copy->buffer_stride;
}
GrdLocalBufferCopy *
grd_local_buffer_copy_new (uint32_t buffer_width,
uint32_t buffer_height)
{
GrdLocalBufferCopy *local_buffer_copy;
uint32_t buffer_stride;
g_assert (buffer_width > 0);
g_assert (buffer_height > 0);
local_buffer_copy = g_object_new (GRD_TYPE_LOCAL_BUFFER_COPY, NULL);
buffer_stride = buffer_width * 4;
local_buffer_copy->buffer = g_new0 (uint8_t, buffer_stride * buffer_height);
local_buffer_copy->buffer_stride = buffer_stride;
return local_buffer_copy;
}
static void
grd_local_buffer_copy_dispose (GObject *object)
{
GrdLocalBufferCopy *local_buffer_copy = GRD_LOCAL_BUFFER_COPY (object);
g_clear_pointer (&local_buffer_copy->buffer, g_free);
G_OBJECT_CLASS (grd_local_buffer_copy_parent_class)->dispose (object);
}
static void
grd_local_buffer_copy_init (GrdLocalBufferCopy *local_buffer)
{
}
static void
grd_local_buffer_copy_class_init (GrdLocalBufferCopyClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdLocalBufferClass *local_buffer_class = GRD_LOCAL_BUFFER_CLASS (klass);
object_class->dispose = grd_local_buffer_copy_dispose;
local_buffer_class->get_buffer = grd_local_buffer_copy_get_buffer;
local_buffer_class->get_buffer_stride =
grd_local_buffer_copy_get_buffer_stride;
}

29
grd-local-buffer-copy.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include "grd-local-buffer.h"
#define GRD_TYPE_LOCAL_BUFFER_COPY (grd_local_buffer_copy_get_type ())
G_DECLARE_FINAL_TYPE (GrdLocalBufferCopy, grd_local_buffer_copy,
GRD, LOCAL_BUFFER_COPY, GrdLocalBuffer)
GrdLocalBufferCopy *grd_local_buffer_copy_new (uint32_t buffer_width,
uint32_t buffer_height);

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2025 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-local-buffer-wrapper-rdp.h"
#include "grd-rdp-buffer.h"
#include "grd-rdp-pw-buffer.h"
struct _GrdLocalBufferWrapperRdp
{
GrdLocalBuffer parent;
uint8_t *buffer;
int32_t buffer_stride;
};
G_DEFINE_TYPE (GrdLocalBufferWrapperRdp, grd_local_buffer_wrapper_rdp,
GRD_TYPE_LOCAL_BUFFER)
void
grd_local_buffer_wrapper_rdp_attach_rdp_buffer (GrdLocalBufferWrapperRdp *buffer_wrapper,
GrdRdpBuffer *rdp_buffer)
{
GrdRdpPwBuffer *rdp_pw_buffer = grd_rdp_buffer_get_rdp_pw_buffer (rdp_buffer);
buffer_wrapper->buffer =
grd_rdp_pw_buffer_get_mapped_data (rdp_pw_buffer,
&buffer_wrapper->buffer_stride);
}
static uint8_t *
grd_local_buffer_wrapper_rdp_get_buffer (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferWrapperRdp *buffer_wrapper =
GRD_LOCAL_BUFFER_WRAPPER_RDP (local_buffer);
return buffer_wrapper->buffer;
}
static uint32_t
grd_local_buffer_wrapper_rdp_get_buffer_stride (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferWrapperRdp *buffer_wrapper =
GRD_LOCAL_BUFFER_WRAPPER_RDP (local_buffer);
return buffer_wrapper->buffer_stride;
}
GrdLocalBufferWrapperRdp *
grd_local_buffer_wrapper_rdp_new (void)
{
return g_object_new (GRD_TYPE_LOCAL_BUFFER_WRAPPER_RDP, NULL);
}
static void
grd_local_buffer_wrapper_rdp_init (GrdLocalBufferWrapperRdp *local_buffer)
{
}
static void
grd_local_buffer_wrapper_rdp_class_init (GrdLocalBufferWrapperRdpClass *klass)
{
GrdLocalBufferClass *local_buffer_class = GRD_LOCAL_BUFFER_CLASS (klass);
local_buffer_class->get_buffer = grd_local_buffer_wrapper_rdp_get_buffer;
local_buffer_class->get_buffer_stride =
grd_local_buffer_wrapper_rdp_get_buffer_stride;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include "grd-local-buffer.h"
#include "grd-types.h"
#define GRD_TYPE_LOCAL_BUFFER_WRAPPER_RDP (grd_local_buffer_wrapper_rdp_get_type ())
G_DECLARE_FINAL_TYPE (GrdLocalBufferWrapperRdp, grd_local_buffer_wrapper_rdp,
GRD, LOCAL_BUFFER_WRAPPER_RDP, GrdLocalBuffer)
GrdLocalBufferWrapperRdp *grd_local_buffer_wrapper_rdp_new (void);
void grd_local_buffer_wrapper_rdp_attach_rdp_buffer (GrdLocalBufferWrapperRdp *buffer_wrapper,
GrdRdpBuffer *rdp_buffer);

51
grd-local-buffer.c Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 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-local-buffer.h"
G_DEFINE_ABSTRACT_TYPE (GrdLocalBuffer, grd_local_buffer,
G_TYPE_OBJECT)
uint8_t *
grd_local_buffer_get_buffer (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferClass *klass = GRD_LOCAL_BUFFER_GET_CLASS (local_buffer);
return klass->get_buffer (local_buffer);
}
uint32_t
grd_local_buffer_get_buffer_stride (GrdLocalBuffer *local_buffer)
{
GrdLocalBufferClass *klass = GRD_LOCAL_BUFFER_GET_CLASS (local_buffer);
return klass->get_buffer_stride (local_buffer);
}
static void
grd_local_buffer_init (GrdLocalBuffer *local_buffer)
{
}
static void
grd_local_buffer_class_init (GrdLocalBufferClass *klass)
{
}

39
grd-local-buffer.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <glib-object.h>
#include <stdint.h>
#define GRD_TYPE_LOCAL_BUFFER (grd_local_buffer_get_type ())
G_DECLARE_DERIVABLE_TYPE (GrdLocalBuffer, grd_local_buffer,
GRD, LOCAL_BUFFER, GObject)
struct _GrdLocalBufferClass
{
GObjectClass parent_class;
uint8_t *(* get_buffer) (GrdLocalBuffer *local_buffer);
uint32_t (* get_buffer_stride) (GrdLocalBuffer *local_buffer);
};
uint8_t *grd_local_buffer_get_buffer (GrdLocalBuffer *local_buffer);
uint32_t grd_local_buffer_get_buffer_stride (GrdLocalBuffer *local_buffer);

87
grd-mime-type.c Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2020 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-mime-type.h"
#include <gio/gio.h>
const char *
grd_mime_type_to_string (GrdMimeType mime_type)
{
switch (mime_type)
{
case GRD_MIME_TYPE_TEXT_PLAIN:
return "text/plain";
case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
return "text/plain;charset=utf-8";
case GRD_MIME_TYPE_TEXT_UTF8_STRING:
return "UTF8_STRING";
case GRD_MIME_TYPE_TEXT_HTML:
return "text/html";
case GRD_MIME_TYPE_IMAGE_BMP:
return "image/bmp";
case GRD_MIME_TYPE_IMAGE_TIFF:
return "image/tiff";
case GRD_MIME_TYPE_IMAGE_GIF:
return "image/gif";
case GRD_MIME_TYPE_IMAGE_JPEG:
return "image/jpeg";
case GRD_MIME_TYPE_IMAGE_PNG:
return "image/png";
case GRD_MIME_TYPE_TEXT_URILIST:
return "text/uri-list";
case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
return "x-special/gnome-copied-files";
default:
return NULL;
}
g_assert_not_reached ();
}
GrdMimeType
grd_mime_type_from_string (const char *mime_type_string)
{
if (strcmp (mime_type_string, "text/plain") == 0)
return GRD_MIME_TYPE_TEXT_PLAIN;
else if (strcmp (mime_type_string, "text/plain;charset=utf-8") == 0)
return GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
else if (strcmp (mime_type_string, "UTF8_STRING") == 0)
return GRD_MIME_TYPE_TEXT_UTF8_STRING;
else if (strcmp (mime_type_string, "text/html") == 0)
return GRD_MIME_TYPE_TEXT_HTML;
else if (strcmp (mime_type_string, "image/bmp") == 0)
return GRD_MIME_TYPE_IMAGE_BMP;
else if (strcmp (mime_type_string, "image/tiff") == 0)
return GRD_MIME_TYPE_IMAGE_TIFF;
else if (strcmp (mime_type_string, "image/gif") == 0)
return GRD_MIME_TYPE_IMAGE_GIF;
else if (strcmp (mime_type_string, "image/jpeg") == 0)
return GRD_MIME_TYPE_IMAGE_JPEG;
else if (strcmp (mime_type_string, "image/png") == 0)
return GRD_MIME_TYPE_IMAGE_PNG;
else if (strcmp (mime_type_string, "text/uri-list") == 0)
return GRD_MIME_TYPE_TEXT_URILIST;
else if (strcmp (mime_type_string, "x-special/gnome-copied-files") == 0)
return GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
return GRD_MIME_TYPE_NONE;
}

52
grd-mime-type.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include <stdint.h>
typedef enum _GrdMimeType
{
GRD_MIME_TYPE_NONE,
GRD_MIME_TYPE_TEXT_PLAIN, /* text/plain */
GRD_MIME_TYPE_TEXT_PLAIN_UTF8, /* text/plain;charset=utf-8 */
GRD_MIME_TYPE_TEXT_UTF8_STRING, /* UTF8_STRING */
GRD_MIME_TYPE_TEXT_HTML, /* text/html */
GRD_MIME_TYPE_IMAGE_BMP, /* image/bmp */
GRD_MIME_TYPE_IMAGE_TIFF, /* image/tiff */
GRD_MIME_TYPE_IMAGE_GIF, /* image/gif */
GRD_MIME_TYPE_IMAGE_JPEG, /* image/jpeg */
GRD_MIME_TYPE_IMAGE_PNG, /* image/png */
GRD_MIME_TYPE_TEXT_URILIST, /* text/uri-list */
GRD_MIME_TYPE_XS_GNOME_COPIED_FILES, /* x-special/gnome-copied-files */
} GrdMimeType;
typedef struct _GrdMimeTypeTable
{
GrdMimeType mime_type;
struct
{
uint32_t format_id;
} rdp;
} GrdMimeTypeTable;
const char *grd_mime_type_to_string (GrdMimeType mime_type);
GrdMimeType grd_mime_type_from_string (const char *mime_type_string);

886
grd-nal-writer.c Normal file
View File

@@ -0,0 +1,886 @@
/*
* Copyright (C) 2022 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-nal-writer.h"
#define H264_PROFILE_HIGH 100
/* See also E.2.1 VUI parameters semantics (Rec. ITU-T H.264 (08/2021)) */
#define H264_Extended_SAR 255
#define H264_NAL_REF_IDC_ZERO 0
#define H264_NAL_REF_IDC_MEDIUM 2
#define H264_NAL_REF_IDC_HIGH 3
/*
* See also Table 7-1 NAL unit type codes, syntax element categories,
* and NAL unit type classes (Rec. ITU-T H.264 (08/2021))
*/
#define H264_NAL_UNIT_TYPE_SLICE_NON_IDR 1
#define H264_NAL_UNIT_TYPE_SLICE_IDR 5
#define H264_NAL_UNIT_TYPE_SPS 7
#define H264_NAL_UNIT_TYPE_PPS 8
#define H264_NAL_UNIT_TYPE_AUD 9
/*
* See also Table 7-6 Name association to slice_type
* (Rec. ITU-T H.264 (08/2021))
*/
#define H264_SLICE_TYPE_P 0
#define H264_SLICE_TYPE_B 1
#define H264_SLICE_TYPE_I 2
#define BITSTREAM_ALLOCATION_STEP 4096
typedef struct
{
uint32_t *buffer;
uint32_t bit_offset;
uint32_t capacity;
} NalBitstream;
struct _GrdNalWriter
{
GObject parent;
NalBitstream *nal_bitstream;
};
G_DEFINE_TYPE (GrdNalWriter, grd_nal_writer, G_TYPE_OBJECT)
static void
start_bitstream (GrdNalWriter *nal_writer)
{
g_assert (!nal_writer->nal_bitstream);
nal_writer->nal_bitstream = g_new0 (NalBitstream, 1);
}
static uint32_t
swap_byte_order (uint32_t value)
{
uint8_t *ptr = (uint8_t *) &value;
return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
}
static uint8_t *
end_bitstream (GrdNalWriter *nal_writer,
uint32_t *bitstream_length)
{
NalBitstream *nal_bitstream = nal_writer->nal_bitstream;
uint32_t *buffer = nal_bitstream->buffer;
uint32_t offset_in_dword;
uint32_t byte_pos;
uint32_t bits_left;
byte_pos = nal_bitstream->bit_offset >> 5;
offset_in_dword = nal_bitstream->bit_offset & 0x1F;
bits_left = 32 - offset_in_dword;
if (bits_left)
buffer[byte_pos] = swap_byte_order (buffer[byte_pos] << bits_left);
*bitstream_length = nal_bitstream->bit_offset;
g_clear_pointer (&nal_writer->nal_bitstream, g_free);
return (uint8_t *) buffer;
}
static void
ensure_remaining_capacity (NalBitstream *nal_bitstream,
uint32_t required_capacity_in_bits)
{
uint32_t new_capacity;
if (required_capacity_in_bits <= nal_bitstream->capacity * 32)
return;
new_capacity = nal_bitstream->capacity + BITSTREAM_ALLOCATION_STEP;
g_assert (required_capacity_in_bits <= new_capacity * 32);
nal_bitstream->buffer = g_realloc (nal_bitstream->buffer,
new_capacity * sizeof (uint32_t));
nal_bitstream->capacity = new_capacity;
}
static void
write_u (GrdNalWriter *nal_writer,
uint32_t value,
uint32_t n_bits)
{
NalBitstream *nal_bitstream = nal_writer->nal_bitstream;
uint32_t offset_in_dword;
uint32_t byte_pos;
uint32_t bits_left;
if (n_bits == 0)
return;
ensure_remaining_capacity (nal_bitstream, nal_bitstream->bit_offset + n_bits);
byte_pos = nal_bitstream->bit_offset >> 5;
offset_in_dword = nal_bitstream->bit_offset & 0x1F;
bits_left = 32 - offset_in_dword;
if (!offset_in_dword)
nal_bitstream->buffer[byte_pos] = 0;
nal_bitstream->bit_offset += n_bits;
if (bits_left > n_bits)
{
nal_bitstream->buffer[byte_pos] <<= n_bits;
nal_bitstream->buffer[byte_pos] |= value;
return;
}
n_bits -= bits_left;
nal_bitstream->buffer[byte_pos] <<= bits_left;
nal_bitstream->buffer[byte_pos] |= value >> n_bits;
nal_bitstream->buffer[byte_pos] =
swap_byte_order (nal_bitstream->buffer[byte_pos]);
nal_bitstream->buffer[++byte_pos] = value;
}
/* Exponential Golomb coding (unsigned) */
static void
write_ue (GrdNalWriter *nal_writer,
uint32_t value)
{
uint32_t value_to_write;
uint32_t n_bits = 0;
uint32_t tmp;
/*
* Write down value + 1, but before that write down n - 1 zeros,
* where n represents the bits to be written for value + 1
*/
value_to_write = value + 1;
tmp = value_to_write;
while (tmp)
{
++n_bits;
tmp >>= 1;
}
if (n_bits > 1)
write_u (nal_writer, 0, n_bits - 1);
write_u (nal_writer, value_to_write, n_bits);
}
/* Exponential Golomb coding (signed) */
static void
write_se (GrdNalWriter *nal_writer,
int32_t value)
{
/*
* If the value is <= 0, map the value to -2 * value (even integer value),
* otherwise map it to 2 * value - 1 (odd integer value)
*/
if (value <= 0)
write_ue (nal_writer, -value << 1);
else
write_ue (nal_writer, (value << 1) - 1);
}
static void
write_nal_start_code_prefix (GrdNalWriter *nal_writer)
{
write_u (nal_writer, 0x00000001, 32);
}
static void
write_nal_header (GrdNalWriter *nal_writer,
uint8_t nal_ref_idc,
uint8_t nal_unit_type)
{
/* See also 7.3.1 NAL unit syntax (Rec. ITU-T H.264 (08/2021)) */
/* forbidden_zero_bit */
write_u (nal_writer, 0, 1);
/* nal_ref_idc */
write_u (nal_writer, nal_ref_idc, 2);
/* nal_unit_type */
write_u (nal_writer, nal_unit_type, 5);
}
static void
byte_align_bitstream (GrdNalWriter *nal_writer)
{
NalBitstream *nal_bitstream = nal_writer->nal_bitstream;
uint32_t offset_in_byte;
uint32_t bits_left;
offset_in_byte = nal_bitstream->bit_offset & 0x7;
bits_left = 8 - offset_in_byte;
if (!bits_left)
return;
/* rbsp_alignment_zero_bit */
write_u (nal_writer, 0, bits_left);
}
static void
write_trailing_bits (GrdNalWriter *nal_writer)
{
/* rbsp_stop_one_bit */
write_u (nal_writer, 1, 1);
byte_align_bitstream (nal_writer);
}
static void
write_access_unit_delimiter (GrdNalWriter *nal_writer)
{
uint32_t primary_pic_type;
primary_pic_type = 1;
write_u (nal_writer, primary_pic_type, 3);
}
uint8_t *
grd_nal_writer_get_aud_bitstream (GrdNalWriter *nal_writer,
uint32_t *bitstream_length)
{
uint8_t *bitstream;
start_bitstream (nal_writer);
write_nal_start_code_prefix (nal_writer);
write_nal_header (nal_writer, H264_NAL_REF_IDC_ZERO, H264_NAL_UNIT_TYPE_AUD);
write_access_unit_delimiter (nal_writer);
write_trailing_bits (nal_writer);
bitstream = end_bitstream (nal_writer, bitstream_length);
g_assert (*bitstream_length % 8 == 0);
return bitstream;
}
static void
write_vui_parameters (GrdNalWriter *nal_writer,
const VAEncSequenceParameterBufferH264 *sequence_param)
{
uint32_t aspect_ratio_info_present_flag;
uint32_t overscan_info_present_flag;
uint32_t video_signal_type_present_flag;
uint32_t chroma_loc_info_present_flag;
uint32_t timing_info_present_flag;
uint32_t nal_hrd_parameters_present_flag;
uint32_t vcl_hrd_parameters_present_flag;
uint32_t pic_struct_present_flag;
uint32_t bitstream_restriction_flag;
g_assert (sequence_param->vui_parameters_present_flag);
aspect_ratio_info_present_flag =
sequence_param->vui_fields.bits.aspect_ratio_info_present_flag;
timing_info_present_flag =
sequence_param->vui_fields.bits.timing_info_present_flag;
bitstream_restriction_flag =
sequence_param->vui_fields.bits.bitstream_restriction_flag;
overscan_info_present_flag = 0;
video_signal_type_present_flag = 0;
chroma_loc_info_present_flag = 0;
nal_hrd_parameters_present_flag = 0;
vcl_hrd_parameters_present_flag = 0;
pic_struct_present_flag = 0;
/*
* See also E.1.1 VUI parameters syntax (Rec. ITU-T H.264 (08/2021))
*
* Not all paths are covered, only the ones relevant for GNOME Remote Desktop.
* Unhandled branches are preceded with an assertion.
*/
/* aspect_ratio_info_present_flag */
write_u (nal_writer, aspect_ratio_info_present_flag, 1);
if (aspect_ratio_info_present_flag)
{
/* aspect_ratio_idc */
write_u (nal_writer, sequence_param->aspect_ratio_idc, 8);
if (sequence_param->aspect_ratio_idc == H264_Extended_SAR)
{
/* sar_width */
write_u (nal_writer, sequence_param->sar_width, 16);
/* sar_height */
write_u (nal_writer, sequence_param->sar_height, 16);
}
}
/* overscan_info_present_flag */
write_u (nal_writer, overscan_info_present_flag, 1);
g_assert (!overscan_info_present_flag);
/* video_signal_type_present_flag */
write_u (nal_writer, video_signal_type_present_flag, 1);
g_assert (!video_signal_type_present_flag);
/* chroma_loc_info_present_flag */
write_u (nal_writer, chroma_loc_info_present_flag, 1);
g_assert (!chroma_loc_info_present_flag);
/* timing_info_present_flag */
write_u (nal_writer, timing_info_present_flag, 1);
if (timing_info_present_flag)
{
uint32_t fixed_frame_rate_flag =
sequence_param->vui_fields.bits.fixed_frame_rate_flag;
/* num_units_in_tick */
write_u (nal_writer, sequence_param->num_units_in_tick, 32);
/* time_scale */
write_u (nal_writer, sequence_param->time_scale, 32);
/* fixed_frame_rate_flag */
write_u (nal_writer, fixed_frame_rate_flag, 1);
}
/* nal_hrd_parameters_present_flag */
write_u (nal_writer, nal_hrd_parameters_present_flag, 1);
g_assert (!nal_hrd_parameters_present_flag);
/* vcl_hrd_parameters_present_flag */
write_u (nal_writer, vcl_hrd_parameters_present_flag, 1);
g_assert (!vcl_hrd_parameters_present_flag);
g_assert (!nal_hrd_parameters_present_flag &&
!vcl_hrd_parameters_present_flag);
/* pic_struct_present_flag */
write_u (nal_writer, pic_struct_present_flag, 1);
/* bitstream_restriction_flag */
write_u (nal_writer, bitstream_restriction_flag, 1);
if (bitstream_restriction_flag)
{
uint32_t motion_vectors_over_pic_boundaries_flag;
uint32_t max_bytes_per_pic_denom;
uint32_t max_bits_per_mb_denom;
uint32_t log2_max_mv_length_horizontal;
uint32_t log2_max_mv_length_vertical;
uint32_t max_num_reorder_frames;
uint32_t max_dec_frame_buffering;
log2_max_mv_length_horizontal =
sequence_param->vui_fields.bits.log2_max_mv_length_horizontal;
log2_max_mv_length_vertical =
sequence_param->vui_fields.bits.log2_max_mv_length_vertical;
motion_vectors_over_pic_boundaries_flag = 1;
max_bytes_per_pic_denom = 0;
max_bits_per_mb_denom = 0;
max_num_reorder_frames = 0;
max_dec_frame_buffering = 1;
/* motion_vectors_over_pic_boundaries_flag */
write_u (nal_writer, motion_vectors_over_pic_boundaries_flag, 1);
/* max_bytes_per_pic_denom */
write_ue (nal_writer, max_bytes_per_pic_denom);
/* max_bits_per_mb_denom */
write_ue (nal_writer, max_bits_per_mb_denom);
/* log2_max_mv_length_horizontal */
write_ue (nal_writer, log2_max_mv_length_horizontal);
/* log2_max_mv_length_vertical */
write_ue (nal_writer, log2_max_mv_length_vertical);
/* max_num_reorder_frames */
write_ue (nal_writer, max_num_reorder_frames);
/* max_dec_frame_buffering */
write_ue (nal_writer, max_dec_frame_buffering);
}
}
static void
write_sps_data (GrdNalWriter *nal_writer,
const VAEncSequenceParameterBufferH264 *sequence_param)
{
uint32_t profile_idc;
uint32_t constraint_set0_flag;
uint32_t constraint_set1_flag;
uint32_t constraint_set2_flag;
uint32_t constraint_set3_flag;
uint32_t constraint_set4_flag;
uint32_t constraint_set5_flag;
uint32_t chroma_format_idc;
uint32_t qpprime_y_zero_transform_bypass_flag;
uint32_t seq_scaling_matrix_present_flag;
uint32_t log2_max_frame_num_minus4;
uint32_t pic_order_cnt_type;
uint32_t gaps_in_frame_num_value_allowed_flag;
uint32_t pic_height_in_map_units;
uint32_t frame_mbs_only_flag;
uint32_t direct_8x8_inference_flag;
g_assert (sequence_param->picture_width_in_mbs > 0);
frame_mbs_only_flag = sequence_param->seq_fields.bits.frame_mbs_only_flag;
/*
* See also 7.4.2.1.1 Sequence parameter set data semantics
* (Rec. ITU-T H.264 (08/2021))
*/
profile_idc = H264_PROFILE_HIGH;
constraint_set0_flag = 0;
constraint_set1_flag = 0;
constraint_set2_flag = 0;
constraint_set3_flag = 0;
g_assert (profile_idc == H264_PROFILE_HIGH);
g_assert (frame_mbs_only_flag == 1);
/* frame_mbs_only_flag is equal to 1 */
constraint_set4_flag = 1;
g_assert (profile_idc == H264_PROFILE_HIGH);
/* No B-slices are present in the coded video sequence */
constraint_set5_flag = 1;
chroma_format_idc = sequence_param->seq_fields.bits.chroma_format_idc;
seq_scaling_matrix_present_flag =
sequence_param->seq_fields.bits.seq_scaling_matrix_present_flag;
log2_max_frame_num_minus4 =
sequence_param->seq_fields.bits.log2_max_frame_num_minus4;
pic_order_cnt_type = sequence_param->seq_fields.bits.pic_order_cnt_type;
direct_8x8_inference_flag =
sequence_param->seq_fields.bits.direct_8x8_inference_flag;
qpprime_y_zero_transform_bypass_flag = 0;
gaps_in_frame_num_value_allowed_flag = 0;
g_assert (frame_mbs_only_flag);
pic_height_in_map_units = sequence_param->picture_height_in_mbs;
g_assert (pic_height_in_map_units > 0);
/*
* See also 7.3.2.1.1 Sequence parameter set data syntax
* (Rec. ITU-T H.264 (08/2021))
*
* Not all paths are covered, only the ones relevant for GNOME Remote Desktop.
* Unhandled branches are preceded with an assertion.
*/
/* profile_idc */
write_u (nal_writer, profile_idc, 8);
/* constraint_set0_flag */
write_u (nal_writer, constraint_set0_flag, 1);
/* constraint_set1_flag */
write_u (nal_writer, constraint_set1_flag, 1);
/* constraint_set2_flag */
write_u (nal_writer, constraint_set2_flag, 1);
/* constraint_set3_flag */
write_u (nal_writer, constraint_set3_flag, 1);
/* constraint_set4_flag */
write_u (nal_writer, constraint_set4_flag, 1);
/* constraint_set5_flag */
write_u (nal_writer, constraint_set5_flag, 1);
/* reserved_zero_2bits */
write_u (nal_writer, 0, 2);
/* level_idc */
write_u (nal_writer, sequence_param->level_idc, 8);
/* seq_parameter_set_id */
write_ue (nal_writer, sequence_param->seq_parameter_set_id);
g_assert (profile_idc == H264_PROFILE_HIGH);
/* chroma_format_idc */
write_ue (nal_writer, chroma_format_idc);
g_assert (chroma_format_idc != 3);
/* bit_depth_luma_minus8 */
write_ue (nal_writer, sequence_param->bit_depth_luma_minus8);
/* bit_depth_chroma_minus8 */
write_ue (nal_writer, sequence_param->bit_depth_chroma_minus8);
/* qpprime_y_zero_transform_bypass_flag */
write_u (nal_writer, qpprime_y_zero_transform_bypass_flag, 1);
/* seq_scaling_matrix_present_flag */
write_u (nal_writer, seq_scaling_matrix_present_flag, 1);
g_assert (!seq_scaling_matrix_present_flag);
/* log2_max_frame_num_minus4 */
write_ue (nal_writer, log2_max_frame_num_minus4);
/* pic_order_cnt_type */
write_ue (nal_writer, pic_order_cnt_type);
if (pic_order_cnt_type == 0)
g_assert_not_reached ();
else if (pic_order_cnt_type == 1)
g_assert_not_reached ();
/* max_num_ref_frames */
write_ue (nal_writer, sequence_param->max_num_ref_frames);
/* gaps_in_frame_num_value_allowed_flag */
write_u (nal_writer, gaps_in_frame_num_value_allowed_flag, 1);
/* pic_width_in_mbs_minus1 */
write_ue (nal_writer, sequence_param->picture_width_in_mbs - 1);
/* pic_height_in_map_units_minus1 */
write_ue (nal_writer, pic_height_in_map_units - 1);
/* frame_mbs_only_flag */
write_u (nal_writer, frame_mbs_only_flag, 1);
g_assert (frame_mbs_only_flag);
/* direct_8x8_inference_flag */
write_u (nal_writer, direct_8x8_inference_flag, 1);
/* frame_cropping_flag */
write_u (nal_writer, sequence_param->frame_cropping_flag, 1);
g_assert (!sequence_param->frame_cropping_flag);
/* vui_parameters_present_flag */
write_u (nal_writer, sequence_param->vui_parameters_present_flag, 1);
if (sequence_param->vui_parameters_present_flag)
write_vui_parameters (nal_writer, sequence_param);
}
uint8_t *
grd_nal_writer_get_sps_bitstream (GrdNalWriter *nal_writer,
const VAEncSequenceParameterBufferH264 *sequence_param,
uint32_t *bitstream_length)
{
uint8_t *bitstream;
start_bitstream (nal_writer);
write_nal_start_code_prefix (nal_writer);
write_nal_header (nal_writer, H264_NAL_REF_IDC_HIGH, H264_NAL_UNIT_TYPE_SPS);
write_sps_data (nal_writer, sequence_param);
write_trailing_bits (nal_writer);
bitstream = end_bitstream (nal_writer, bitstream_length);
g_assert (*bitstream_length % 8 == 0);
return bitstream;
}
static void
write_pps_data (GrdNalWriter *nal_writer,
const VAEncPictureParameterBufferH264 *picture_param)
{
uint32_t entropy_coding_mode_flag;
uint32_t bottom_field_pic_order_in_frame_present_flag;
uint32_t num_slice_groups_minus1;
uint32_t weighted_pred_flag;
uint32_t weighted_bipred_idc;
uint32_t pic_init_qs_minus26;
uint32_t deblocking_filter_control_present_flag;
uint32_t constrained_intra_pred_flag;
uint32_t redundant_pic_cnt_present_flag;
uint32_t transform_8x8_mode_flag;
uint32_t pic_scaling_matrix_present_flag;
entropy_coding_mode_flag =
picture_param->pic_fields.bits.entropy_coding_mode_flag;
bottom_field_pic_order_in_frame_present_flag =
picture_param->pic_fields.bits.pic_order_present_flag;
weighted_pred_flag = picture_param->pic_fields.bits.weighted_pred_flag;
weighted_bipred_idc = picture_param->pic_fields.bits.weighted_bipred_idc;
deblocking_filter_control_present_flag =
picture_param->pic_fields.bits.deblocking_filter_control_present_flag;
constrained_intra_pred_flag =
picture_param->pic_fields.bits.constrained_intra_pred_flag;
redundant_pic_cnt_present_flag =
picture_param->pic_fields.bits.redundant_pic_cnt_present_flag;
transform_8x8_mode_flag =
picture_param->pic_fields.bits.transform_8x8_mode_flag;
pic_scaling_matrix_present_flag =
picture_param->pic_fields.bits.pic_scaling_matrix_present_flag;
num_slice_groups_minus1 = 0;
pic_init_qs_minus26 = 0;
/*
* See also 7.3.2.2 Picture parameter set RBSP syntax
* (Rec. ITU-T H.264 (08/2021))
*
* Not all paths are covered, only the ones relevant for GNOME Remote Desktop.
* Unhandled branches are preceded with an assertion.
*/
/* pic_parameter_set_id */
write_ue (nal_writer, picture_param->pic_parameter_set_id);
/* seq_parameter_set_id */
write_ue (nal_writer, picture_param->seq_parameter_set_id);
/* entropy_coding_mode_flag */
write_u (nal_writer, entropy_coding_mode_flag, 1);
/* bottom_field_pic_order_in_frame_present_flag */
write_u (nal_writer, bottom_field_pic_order_in_frame_present_flag, 1);
/* num_slice_groups_minus1 */
write_ue (nal_writer, num_slice_groups_minus1);
g_assert (num_slice_groups_minus1 == 0);
/* num_ref_idx_l0_default_active_minus1 */
write_ue (nal_writer, picture_param->num_ref_idx_l0_active_minus1);
/* num_ref_idx_l1_default_active_minus1 */
write_ue (nal_writer, picture_param->num_ref_idx_l1_active_minus1);
/* weighted_pred_flag */
write_u (nal_writer, weighted_pred_flag, 1);
/* weighted_bipred_idc */
write_u (nal_writer, weighted_bipred_idc, 2);
/* pic_init_qp_minus26 */
write_se (nal_writer, picture_param->pic_init_qp - 26);
/* pic_init_qs_minus26 */
write_se (nal_writer, pic_init_qs_minus26);
/* chroma_qp_index_offset */
write_se (nal_writer, picture_param->chroma_qp_index_offset);
/* deblocking_filter_control_present_flag */
write_u (nal_writer, deblocking_filter_control_present_flag, 1);
/* constrained_intra_pred_flag */
write_u (nal_writer, constrained_intra_pred_flag, 1);
/* redundant_pic_cnt_present_flag */
write_u (nal_writer, redundant_pic_cnt_present_flag, 1);
/* more_rbsp_data */
/* transform_8x8_mode_flag */
write_u (nal_writer, transform_8x8_mode_flag, 1);
/* pic_scaling_matrix_present_flag */
write_u (nal_writer, pic_scaling_matrix_present_flag, 1);
g_assert (!pic_scaling_matrix_present_flag);
/* second_chroma_qp_index_offset */
write_se (nal_writer, picture_param->second_chroma_qp_index_offset);
}
uint8_t *
grd_nal_writer_get_pps_bitstream (GrdNalWriter *nal_writer,
const VAEncPictureParameterBufferH264 *picture_param,
uint32_t *bitstream_length)
{
uint8_t *bitstream;
start_bitstream (nal_writer);
write_nal_start_code_prefix (nal_writer);
write_nal_header (nal_writer, H264_NAL_REF_IDC_HIGH, H264_NAL_UNIT_TYPE_PPS);
write_pps_data (nal_writer, picture_param);
write_trailing_bits (nal_writer);
bitstream = end_bitstream (nal_writer, bitstream_length);
g_assert (*bitstream_length % 8 == 0);
return bitstream;
}
static void
write_ref_pic_list_modification (GrdNalWriter *nal_writer,
const VAEncSliceParameterBufferH264 *slice_param)
{
if (slice_param->slice_type != H264_SLICE_TYPE_I)
{
uint32_t ref_pic_list_modification_flag_l0;
ref_pic_list_modification_flag_l0 = 0;
/* ref_pic_list_modification_flag_l0 */
write_u (nal_writer, ref_pic_list_modification_flag_l0, 1);
g_assert (!ref_pic_list_modification_flag_l0);
}
g_assert (slice_param->slice_type != H264_SLICE_TYPE_B);
}
static void
write_dec_ref_pic_marking (GrdNalWriter *nal_writer,
const VAEncPictureParameterBufferH264 *picture_param)
{
if (picture_param->pic_fields.bits.idr_pic_flag)
{
uint32_t no_output_of_prior_pics_flag;
uint32_t long_term_reference_flag;
no_output_of_prior_pics_flag = 0;
long_term_reference_flag = 0;
/* no_output_of_prior_pics_flag */
write_u (nal_writer, no_output_of_prior_pics_flag, 1);
/* long_term_reference_flag */
write_u (nal_writer, long_term_reference_flag, 1);
}
else
{
uint32_t adaptive_ref_pic_marking_mode_flag;
adaptive_ref_pic_marking_mode_flag = 0;
/* adaptive_ref_pic_marking_mode_flag */
write_u (nal_writer, adaptive_ref_pic_marking_mode_flag, 1);
g_assert (!adaptive_ref_pic_marking_mode_flag);
}
}
static void
write_slice_header (GrdNalWriter *nal_writer,
const VAEncSliceParameterBufferH264 *slice_param,
const VAEncSequenceParameterBufferH264 *sequence_param,
const VAEncPictureParameterBufferH264 *picture_param,
const uint8_t nal_ref_idc)
{
uint32_t separate_colour_plane_flag;
uint32_t log2_max_frame_num;
uint16_t frame_num;
uint32_t frame_mbs_only_flag;
uint32_t pic_order_cnt_type;
uint32_t redundant_pic_cnt_present_flag;
uint32_t weighted_pred_flag;
uint32_t weighted_bipred_idc;
uint32_t entropy_coding_mode_flag;
uint32_t deblocking_filter_control_present_flag;
uint32_t num_slice_groups_minus1;
frame_num = picture_param->frame_num;
log2_max_frame_num =
sequence_param->seq_fields.bits.log2_max_frame_num_minus4 + 4;
frame_mbs_only_flag = sequence_param->seq_fields.bits.frame_mbs_only_flag;
pic_order_cnt_type = sequence_param->seq_fields.bits.pic_order_cnt_type;
redundant_pic_cnt_present_flag =
picture_param->pic_fields.bits.redundant_pic_cnt_present_flag;
weighted_pred_flag = picture_param->pic_fields.bits.weighted_pred_flag;
weighted_bipred_idc = picture_param->pic_fields.bits.weighted_bipred_idc;
entropy_coding_mode_flag =
picture_param->pic_fields.bits.entropy_coding_mode_flag;
deblocking_filter_control_present_flag =
picture_param->pic_fields.bits.deblocking_filter_control_present_flag;
separate_colour_plane_flag = 0;
num_slice_groups_minus1 = 0;
/*
* See also 7.3.3 Slice header syntax (Rec. ITU-T H.264 (08/2021))
*
* Not all paths are covered, only the ones relevant for GNOME Remote Desktop.
* Unhandled branches are preceded with an assertion.
*/
/* first_mb_in_slice */
write_ue (nal_writer, slice_param->macroblock_address);
/* slice_type */
write_ue (nal_writer, slice_param->slice_type);
/* pic_parameter_set_id */
write_ue (nal_writer, slice_param->pic_parameter_set_id);
g_assert (!separate_colour_plane_flag);
/* frame_num */
write_u (nal_writer, frame_num, log2_max_frame_num);
g_assert (frame_mbs_only_flag);
if (picture_param->pic_fields.bits.idr_pic_flag)
{
/* idr_pic_id */
write_ue (nal_writer, slice_param->idr_pic_id);
}
if (pic_order_cnt_type == 0)
g_assert_not_reached ();
if (pic_order_cnt_type == 1)
g_assert_not_reached ();
g_assert (!redundant_pic_cnt_present_flag);
g_assert (slice_param->slice_type != H264_SLICE_TYPE_B);
if (slice_param->slice_type == H264_SLICE_TYPE_P)
{
/* num_ref_idx_active_override_flag */
write_u (nal_writer, slice_param->num_ref_idx_active_override_flag, 1);
g_assert (!slice_param->num_ref_idx_active_override_flag);
}
write_ref_pic_list_modification (nal_writer, slice_param);
g_assert (!weighted_pred_flag && !weighted_bipred_idc);
if (nal_ref_idc)
write_dec_ref_pic_marking (nal_writer, picture_param);
if (entropy_coding_mode_flag && slice_param->slice_type != H264_SLICE_TYPE_I)
{
/* cabac_init_idc */
write_ue (nal_writer, slice_param->cabac_init_idc);
}
/* slice_qp_delta */
write_se (nal_writer, slice_param->slice_qp_delta);
g_assert (slice_param->slice_type == H264_SLICE_TYPE_I ||
slice_param->slice_type == H264_SLICE_TYPE_P);
g_assert (!deblocking_filter_control_present_flag);
g_assert (num_slice_groups_minus1 == 0);
}
static void
get_nal_header_parameters (const VAEncSliceParameterBufferH264 *slice_param,
const VAEncPictureParameterBufferH264 *picture_param,
uint8_t *nal_ref_idc,
uint8_t *nal_unit_type)
{
switch (slice_param->slice_type)
{
case H264_SLICE_TYPE_I:
*nal_ref_idc = H264_NAL_REF_IDC_HIGH;
if (picture_param->pic_fields.bits.idr_pic_flag)
*nal_unit_type = H264_NAL_UNIT_TYPE_SLICE_IDR;
else
*nal_unit_type = H264_NAL_UNIT_TYPE_SLICE_NON_IDR;
break;
case H264_SLICE_TYPE_P:
*nal_ref_idc = H264_NAL_REF_IDC_MEDIUM;
*nal_unit_type = H264_NAL_UNIT_TYPE_SLICE_NON_IDR;
break;
default:
g_assert_not_reached ();
}
}
uint8_t *
grd_nal_writer_get_slice_header_bitstream (GrdNalWriter *nal_writer,
const VAEncSliceParameterBufferH264 *slice_param,
const VAEncSequenceParameterBufferH264 *sequence_param,
const VAEncPictureParameterBufferH264 *picture_param,
uint32_t *bitstream_length)
{
uint8_t nal_ref_idc;
uint8_t nal_unit_type;
get_nal_header_parameters (slice_param, picture_param,
&nal_ref_idc, &nal_unit_type);
start_bitstream (nal_writer);
write_nal_start_code_prefix (nal_writer);
write_nal_header (nal_writer, nal_ref_idc, nal_unit_type);
write_slice_header (nal_writer, slice_param, sequence_param, picture_param,
nal_ref_idc);
return end_bitstream (nal_writer, bitstream_length);
}
GrdNalWriter *
grd_nal_writer_new (void)
{
return g_object_new (GRD_TYPE_NAL_WRITER, NULL);
}
static void
grd_nal_writer_dispose (GObject *object)
{
GrdNalWriter *nal_writer = GRD_NAL_WRITER (object);
g_assert (!nal_writer->nal_bitstream);
G_OBJECT_CLASS (grd_nal_writer_parent_class)->dispose (object);
}
static void
grd_nal_writer_init (GrdNalWriter *nal_writer)
{
}
static void
grd_nal_writer_class_init (GrdNalWriterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_nal_writer_dispose;
}

46
grd-nal-writer.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2022 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.
*/
#pragma once
#include <glib-object.h>
#include <va/va.h>
#define GRD_TYPE_NAL_WRITER (grd_nal_writer_get_type ())
G_DECLARE_FINAL_TYPE (GrdNalWriter, grd_nal_writer,
GRD, NAL_WRITER, GObject)
GrdNalWriter *grd_nal_writer_new (void);
uint8_t *grd_nal_writer_get_aud_bitstream (GrdNalWriter *nal_writer,
uint32_t *bitstream_length);
uint8_t *grd_nal_writer_get_sps_bitstream (GrdNalWriter *nal_writer,
const VAEncSequenceParameterBufferH264 *sequence_param,
uint32_t *bitstream_length);
uint8_t *grd_nal_writer_get_pps_bitstream (GrdNalWriter *nal_writer,
const VAEncPictureParameterBufferH264 *picture_param,
uint32_t *bitstream_length);
uint8_t *grd_nal_writer_get_slice_header_bitstream (GrdNalWriter *nal_writer,
const VAEncSliceParameterBufferH264 *slice_param,
const VAEncSequenceParameterBufferH264 *sequence_param,
const VAEncPictureParameterBufferH264 *picture_param,
uint32_t *bitstream_length);

207
grd-pipewire-utils.c Normal file
View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2015 Red Hat Inc.
* Copyright (C) 2020 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-pipewire-utils.h"
#include <drm_fourcc.h>
#include <spa/param/video/raw.h>
#include <spa/utils/result.h>
static void
pipewire_source_unref (GrdPipeWireSource *pipewire_source);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrdPipeWireSource, pipewire_source_unref)
static gboolean
pipewire_loop_source_prepare (GSource *base,
int *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
pipewire_loop_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source;
int result;
result = pw_loop_iterate (pipewire_source->pipewire_loop, 0);
if (result < 0)
{
g_warning ("%s pw_loop_iterate() failed: %s", pipewire_source->message_tag,
spa_strerror (result));
}
return G_SOURCE_CONTINUE;
}
static void
pipewire_loop_source_finalize (GSource *source)
{
GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source;
if (pipewire_source->pipewire_loop)
{
pw_loop_leave (pipewire_source->pipewire_loop);
pw_loop_destroy (pipewire_source->pipewire_loop);
}
g_clear_pointer (&pipewire_source->message_tag, g_free);
}
static GSourceFuncs pipewire_source_funcs =
{
pipewire_loop_source_prepare,
NULL,
pipewire_loop_source_dispatch,
pipewire_loop_source_finalize,
};
GrdPipeWireSource *
grd_pipewire_source_new (const char *message_tag,
GError **error)
{
g_autoptr (GrdPipeWireSource) pipewire_source = NULL;
g_assert (strlen (message_tag) > 0);
pipewire_source =
(GrdPipeWireSource *) g_source_new (&pipewire_source_funcs,
sizeof (GrdPipeWireSource));
pipewire_source->message_tag = g_strdup_printf ("[%s]", message_tag);
pipewire_source->pipewire_loop = pw_loop_new (NULL);
if (!pipewire_source->pipewire_loop)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create PipeWire loop");
return NULL;
}
g_source_add_unix_fd (&pipewire_source->base,
pw_loop_get_fd (pipewire_source->pipewire_loop),
G_IO_IN | G_IO_ERR);
pw_loop_enter (pipewire_source->pipewire_loop);
return g_steal_pointer (&pipewire_source);
}
GrdPipeWireSource *
grd_attached_pipewire_source_new (const char *message_tag,
GError **error)
{
GrdPipeWireSource *pipewire_source;
pipewire_source = grd_pipewire_source_new (message_tag, error);
if (!pipewire_source)
return NULL;
g_source_attach (&pipewire_source->base, NULL);
return pipewire_source;
}
static void
pipewire_source_unref (GrdPipeWireSource *pipewire_source)
{
g_source_unref (&pipewire_source->base);
}
gboolean
grd_pipewire_buffer_has_pointer_bitmap (struct pw_buffer *buffer)
{
struct spa_meta_cursor *spa_meta_cursor;
spa_meta_cursor = spa_buffer_find_meta_data (buffer->buffer, SPA_META_Cursor,
sizeof *spa_meta_cursor);
if (spa_meta_cursor && spa_meta_cursor_is_valid (spa_meta_cursor))
{
struct spa_meta_bitmap *spa_meta_bitmap = NULL;
if (spa_meta_cursor->bitmap_offset)
{
spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor,
spa_meta_cursor->bitmap_offset,
struct spa_meta_bitmap);
}
if (spa_meta_bitmap)
return TRUE;
}
return FALSE;
}
gboolean
grd_pipewire_buffer_has_frame_data (struct pw_buffer *buffer)
{
return buffer->buffer->datas[0].chunk->size > 0;
}
gboolean
grd_spa_pixel_format_to_grd_pixel_format (uint32_t spa_format,
GrdPixelFormat *out_format)
{
if (spa_format == SPA_VIDEO_FORMAT_RGBA)
*out_format = GRD_PIXEL_FORMAT_RGBA8888;
else
return FALSE;
return TRUE;
}
static struct
{
enum spa_video_format spa_format;
uint32_t drm_format;
int bpp;
} format_table[] = {
{ SPA_VIDEO_FORMAT_ARGB, DRM_FORMAT_BGRA8888, 4 },
{ SPA_VIDEO_FORMAT_BGRA, DRM_FORMAT_ARGB8888, 4 },
{ SPA_VIDEO_FORMAT_xRGB, DRM_FORMAT_BGRX8888, 4 },
{ SPA_VIDEO_FORMAT_BGRx, DRM_FORMAT_XRGB8888, 4 },
};
void
grd_get_spa_format_details (enum spa_video_format spa_format,
uint32_t *drm_format,
int *bpp)
{
int i;
for (i = 0; i < G_N_ELEMENTS (format_table); i++)
{
if (format_table[i].spa_format == spa_format)
{
if (drm_format)
*drm_format = format_table[i].drm_format;
if (bpp)
*bpp = format_table[i].bpp;
return;
}
}
g_assert_not_reached ();
}

56
grd-pipewire-utils.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include <gio/gio.h>
#include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h>
#include <stdint.h>
#include "grd-types.h"
#define CURSOR_META_SIZE(width, height) \
(sizeof(struct spa_meta_cursor) + \
sizeof(struct spa_meta_bitmap) + width * height * 4)
typedef struct _GrdPipeWireSource
{
GSource base;
char *message_tag;
struct pw_loop *pipewire_loop;
} GrdPipeWireSource;
GrdPipeWireSource *grd_pipewire_source_new (const char *message_tag,
GError **error);
GrdPipeWireSource *grd_attached_pipewire_source_new (const char *message_tag,
GError **error);
gboolean grd_pipewire_buffer_has_pointer_bitmap (struct pw_buffer *buffer);
gboolean grd_pipewire_buffer_has_frame_data (struct pw_buffer *buffer);
gboolean grd_spa_pixel_format_to_grd_pixel_format (uint32_t spa_format,
GrdPixelFormat *out_format);
void grd_get_spa_format_details (enum spa_video_format spa_format,
uint32_t *drm_format,
int *bpp);

44
grd-private.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2015 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#pragma once
#define GRD_DAEMON_USER_APPLICATION_ID "org.gnome.RemoteDesktop.User"
#define GRD_DAEMON_HEADLESS_APPLICATION_ID "org.gnome.RemoteDesktop.Headless"
#define GRD_DAEMON_SYSTEM_APPLICATION_ID "org.gnome.RemoteDesktop"
#define GRD_DAEMON_HANDOVER_APPLICATION_ID "org.gnome.RemoteDesktop.Handover"
#define REMOTE_DESKTOP_BUS_NAME "org.gnome.RemoteDesktop"
#define REMOTE_DESKTOP_OBJECT_PATH "/org/gnome/RemoteDesktop"
#define REMOTE_DESKTOP_CONFIGURATION_BUS_NAME "org.gnome.RemoteDesktop.Configuration"
#define REMOTE_DESKTOP_CONFIGURATION_OBJECT_PATH "/org/gnome/RemoteDesktop/Configuration"
#define REMOTE_DESKTOP_CLIENT_OBJECT_PATH "/org/gnome/RemoteDesktop/Client"
#define REMOTE_DESKTOP_DISPATCHER_OBJECT_PATH "/org/gnome/RemoteDesktop/Rdp/Dispatcher"
#define REMOTE_DESKTOP_HANDOVERS_OBJECT_PATH "/org/gnome/RemoteDesktop/Rdp/Handovers"
#define GRD_RDP_SERVER_OBJECT_PATH "/org/gnome/RemoteDesktop/Rdp/Server"
#define GRD_VNC_SERVER_OBJECT_PATH "/org/gnome/RemoteDesktop/Vnc/Server"
#define MUTTER_REMOTE_DESKTOP_BUS_NAME "org.gnome.Mutter.RemoteDesktop"
#define MUTTER_REMOTE_DESKTOP_OBJECT_PATH "/org/gnome/Mutter/RemoteDesktop"
#define MUTTER_SCREEN_CAST_BUS_NAME "org.gnome.Mutter.ScreenCast"
#define MUTTER_SCREEN_CAST_OBJECT_PATH "/org/gnome/Mutter/ScreenCast"
#define GDM_BUS_NAME "org.gnome.DisplayManager"
#define GDM_REMOTE_DISPLAY_FACTORY_OBJECT_PATH "/org/gnome/DisplayManager/RemoteDisplayFactory"
#define GDM_OBJECT_MANAGER_OBJECT_PATH "/org/gnome/DisplayManager/Displays"

207
grd-prompt.c Normal file
View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2018 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 <libnotify/notify.h>
#include "grd-prompt.h"
typedef struct _GrdPromptResult
{
GrdPromptResponse response;
} GrdPromptResult;
struct _GrdPrompt
{
GObject parent;
};
G_DEFINE_TYPE (GrdPrompt, grd_prompt, G_TYPE_OBJECT)
static void
handle_notification_response (NotifyNotification *notification,
char *response,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
if (g_strcmp0 (response, "accept") == 0)
{
g_task_return_int (task, GRD_PROMPT_RESPONSE_ACCEPT);
}
else if (g_strcmp0 (response, "cancel") == 0 ||
g_strcmp0 (response, "closed") == 0)
{
g_task_return_int (task, GRD_PROMPT_RESPONSE_CANCEL);
}
else
{
g_warning ("Unknown prompt response '%s'", response);
g_task_return_int (task, GRD_PROMPT_RESPONSE_CANCEL);
}
}
static void
on_notification_closed (NotifyNotification *notification,
gpointer user_data)
{
handle_notification_response (notification, "closed", user_data);
}
static gboolean
cancelled_idle_callback (gpointer user_data)
{
GTask *task = G_TASK (user_data);
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
"Prompt was cancelled");
return G_SOURCE_REMOVE;
}
static void
on_cancellable_cancelled (GCancellable *cancellable,
GTask *task)
{
NotifyNotification *notification =
NOTIFY_NOTIFICATION (g_task_get_task_data (task));
g_signal_handlers_disconnect_by_func (notification,
G_CALLBACK (on_notification_closed),
task);
notify_notification_close (notification, NULL);
g_idle_add (cancelled_idle_callback, task);
}
static gboolean
show_notification_idle_callback (gpointer user_data)
{
GTask *task = G_TASK (user_data);
GCancellable *cancellable = g_task_get_cancellable (task);
NotifyNotification *notification;
GError *error = NULL;
if (g_cancellable_is_cancelled (cancellable))
return G_SOURCE_REMOVE;
notification = g_task_get_task_data (task);
if (!notify_notification_show (notification, &error))
g_task_return_error (task, error);
return G_SOURCE_REMOVE;
}
void
grd_prompt_query_async (GrdPrompt *prompt,
GrdPromptDefinition *prompt_definition,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
NotifyNotification *notification;
GTask *task;
task = g_task_new (G_OBJECT (prompt), cancellable, callback, user_data);
g_assert (prompt_definition);
g_assert (prompt_definition->summary ||
prompt_definition->body);
notification = notify_notification_new (prompt_definition->summary,
prompt_definition->body,
"preferences-desktop-remote-desktop");
notify_notification_set_urgency (notification, NOTIFY_URGENCY_CRITICAL);
if (prompt_definition->cancel_label)
{
notify_notification_add_action (notification,
"cancel",
prompt_definition->cancel_label,
handle_notification_response,
task, NULL);
}
if (prompt_definition->accept_label)
{
notify_notification_add_action (notification,
"accept",
prompt_definition->accept_label,
handle_notification_response,
task, NULL);
}
g_task_set_task_data (task, notification, g_object_unref);
g_signal_connect (notification, "closed",
G_CALLBACK (on_notification_closed), task);
g_cancellable_connect (cancellable,
G_CALLBACK (on_cancellable_cancelled),
task, NULL);
g_idle_add (show_notification_idle_callback, task);
}
gboolean
grd_prompt_query_finish (GrdPrompt *prompt,
GAsyncResult *result,
GrdPromptResponse *out_response,
GError **error)
{
g_autoptr(GTask) task = G_TASK (result);
GCancellable *cancellable;
GrdPromptResponse response;
cancellable = g_task_get_cancellable (task);
g_signal_handlers_disconnect_by_func (cancellable,
G_CALLBACK (on_cancellable_cancelled),
task);
response = g_task_propagate_int (task, error);
if (response == -1)
return FALSE;
*out_response = response;
return TRUE;
}
void
grd_prompt_definition_free (GrdPromptDefinition *prompt_definition)
{
g_clear_pointer (&prompt_definition->summary, g_free);
g_clear_pointer (&prompt_definition->body, g_free);
g_clear_pointer (&prompt_definition->accept_label, g_free);
g_clear_pointer (&prompt_definition->cancel_label, g_free);
g_free (prompt_definition);
}
static void
grd_prompt_init (GrdPrompt *prompt)
{
}
static void
grd_prompt_class_init (GrdPromptClass *klass)
{
notify_init (g_get_application_name ());
}

53
grd-prompt.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 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.
*
*/
#pragma once
typedef enum _GrdPromptResponse
{
GRD_PROMPT_RESPONSE_ACCEPT,
GRD_PROMPT_RESPONSE_CANCEL
} GrdPromptResponse;
typedef struct _GrdPromptDefinition
{
char *summary;
char *body;
char *accept_label;
char *cancel_label;
} GrdPromptDefinition;
#define GRD_TYPE_PROMPT (grd_prompt_get_type ())
G_DECLARE_FINAL_TYPE (GrdPrompt, grd_prompt, GRD, PROMPT, GObject)
void grd_prompt_query_async (GrdPrompt *prompt,
GrdPromptDefinition *prompt_definition,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean grd_prompt_query_finish (GrdPrompt *prompt,
GAsyncResult *result,
GrdPromptResponse *response,
GError **error);
void grd_prompt_definition_free (GrdPromptDefinition *prompt_definition);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GrdPromptDefinition, grd_prompt_definition_free)

View File

@@ -0,0 +1,387 @@
/*
* Copyright (C) 2022 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-audio-output-stream.h"
#include <gio/gio.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/props.h>
#include "grd-rdp-dvc-audio-playback.h"
struct _GrdRdpAudioOutputStream
{
GObject parent;
uint32_t target_node_id;
GrdRdpDvcAudioPlayback *audio_playback;
struct pw_node *pipewire_node;
struct spa_hook pipewire_node_listener;
struct pw_stream *pipewire_stream;
struct spa_hook pipewire_stream_listener;
GMutex volume_mutex;
gboolean audio_muted;
float volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_volumes;
};
G_DEFINE_TYPE (GrdRdpAudioOutputStream, grd_rdp_audio_output_stream, G_TYPE_OBJECT)
void
grd_rdp_audio_output_stream_set_active (GrdRdpAudioOutputStream *audio_output_stream,
gboolean active)
{
pw_stream_set_active (audio_output_stream->pipewire_stream, active);
}
void
grd_rdp_audio_output_stream_get_volume_data (GrdRdpAudioOutputStream *audio_output_stream,
GrdRdpAudioVolumeData *volume_data)
{
uint32_t i;
g_mutex_lock (&audio_output_stream->volume_mutex);
volume_data->audio_muted = audio_output_stream->audio_muted;
volume_data->n_volumes = audio_output_stream->n_volumes;
for (i = 0; i < audio_output_stream->n_volumes; ++i)
volume_data->volumes[i] = audio_output_stream->volumes[i];
g_mutex_unlock (&audio_output_stream->volume_mutex);
}
static void
pipewire_node_info (void *user_data,
const struct pw_node_info *info)
{
GrdRdpAudioOutputStream *audio_output_stream = user_data;
uint32_t i;
if (!(info->change_mask & PW_NODE_CHANGE_MASK_PARAMS))
return;
for (i = 0; i < info->n_params; ++i)
{
if (info->params[i].id == SPA_PARAM_Props &&
info->params[i].flags & SPA_PARAM_INFO_READ)
{
pw_node_enum_params (audio_output_stream->pipewire_node,
0, SPA_PARAM_Props, 0, -1, NULL);
}
}
}
static void
pipewire_node_param (void *user_data,
int seq,
uint32_t id,
uint32_t index,
uint32_t next,
const struct spa_pod *param)
{
GrdRdpAudioOutputStream *audio_output_stream = user_data;
const struct spa_pod_object *object = (const struct spa_pod_object *) param;
struct spa_pod_prop *prop;
if (id != SPA_PARAM_Props)
return;
SPA_POD_OBJECT_FOREACH (object, prop)
{
if (prop->key == SPA_PROP_mute)
{
bool mute;
if (spa_pod_get_bool (&prop->value, &mute) < 0)
continue;
g_mutex_lock (&audio_output_stream->volume_mutex);
audio_output_stream->audio_muted = !!mute;
g_mutex_unlock (&audio_output_stream->volume_mutex);
}
else if (prop->key == SPA_PROP_channelVolumes)
{
g_mutex_lock (&audio_output_stream->volume_mutex);
audio_output_stream->n_volumes =
spa_pod_copy_array (&prop->value, SPA_TYPE_Float,
audio_output_stream->volumes,
SPA_AUDIO_MAX_CHANNELS);
g_mutex_unlock (&audio_output_stream->volume_mutex);
}
}
}
static const struct pw_node_events pipewire_node_events =
{
.version = PW_VERSION_NODE_EVENTS,
.info = pipewire_node_info,
.param = pipewire_node_param,
};
static void
pipewire_stream_state_changed (void *user_data,
enum pw_stream_state old,
enum pw_stream_state state,
const char *error)
{
GrdRdpAudioOutputStream *audio_output_stream = user_data;
g_debug ("[RDP.AUDIO_PLAYBACK] Node %u: PipeWire stream state changed from "
"%s to %s", audio_output_stream->target_node_id,
pw_stream_state_as_string (old),
pw_stream_state_as_string (state));
switch (state)
{
case PW_STREAM_STATE_ERROR:
g_warning ("[RDP.AUDIO_PLAYBACK] Node %u: PipeWire stream error: %s",
audio_output_stream->target_node_id, error);
break;
case PW_STREAM_STATE_PAUSED:
case PW_STREAM_STATE_STREAMING:
case PW_STREAM_STATE_UNCONNECTED:
case PW_STREAM_STATE_CONNECTING:
break;
}
}
static void
pipewire_stream_param_changed (void *user_data,
uint32_t id,
const struct spa_pod *param)
{
GrdRdpAudioOutputStream *audio_output_stream = user_data;
struct spa_pod_builder pod_builder;
const struct spa_pod *params[1];
uint8_t params_buffer[1024];
if (!param || id != SPA_PARAM_Format)
return;
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_dataType, SPA_POD_Int (1 << SPA_DATA_MemPtr));
pw_stream_update_params (audio_output_stream->pipewire_stream, params, 1);
}
static void
pipewire_stream_process (void *user_data)
{
GrdRdpAudioOutputStream *audio_output_stream = user_data;
struct pw_buffer *next_buffer;
struct pw_buffer *buffer = NULL;
int16_t *data;
uint32_t offset;
uint32_t size;
next_buffer = pw_stream_dequeue_buffer (audio_output_stream->pipewire_stream);
while (next_buffer)
{
buffer = next_buffer;
next_buffer = pw_stream_dequeue_buffer (audio_output_stream->pipewire_stream);
if (next_buffer)
pw_stream_queue_buffer (audio_output_stream->pipewire_stream, buffer);
}
if (!buffer)
return;
offset = buffer->buffer->datas[0].chunk->offset;
size = buffer->buffer->datas[0].chunk->size;
data = buffer->buffer->datas[0].data + offset;
if (size > 0)
{
GrdRdpDvcAudioPlayback *audio_playback =
audio_output_stream->audio_playback;
uint32_t node_id = audio_output_stream->target_node_id;
GrdRdpAudioVolumeData volume_data = {};
grd_rdp_audio_output_stream_get_volume_data (audio_output_stream,
&volume_data);
grd_rdp_dvc_audio_playback_maybe_submit_samples (audio_playback, node_id,
&volume_data, data, size);
}
pw_stream_queue_buffer (audio_output_stream->pipewire_stream, buffer);
}
static const struct pw_stream_events pipewire_stream_events =
{
.version = PW_VERSION_STREAM_EVENTS,
.state_changed = pipewire_stream_state_changed,
.param_changed = pipewire_stream_param_changed,
.process = pipewire_stream_process,
};
static gboolean
connect_to_stream (GrdRdpAudioOutputStream *audio_output_stream,
struct pw_core *pipewire_core,
uint32_t target_node_id,
uint32_t n_samples_per_sec,
uint32_t n_channels,
uint32_t *position,
GError **error)
{
struct spa_pod_builder pod_builder;
const struct spa_pod *params[1];
uint8_t params_buffer[1024];
int result;
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_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_Id (SPA_AUDIO_FORMAT_S16),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int (n_samples_per_sec),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int (n_channels),
SPA_FORMAT_AUDIO_position, SPA_POD_Array (sizeof (uint32_t), SPA_TYPE_Id,
n_channels, position),
0);
audio_output_stream->pipewire_stream =
pw_stream_new (pipewire_core,
"GRD::RDP::AUDIO_PLAYBACK",
pw_properties_new (PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_NODE_FORCE_QUANTUM, "256",
NULL));
if (!audio_output_stream->pipewire_stream)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create PipeWire stream");
return FALSE;
}
pw_stream_add_listener (audio_output_stream->pipewire_stream,
&audio_output_stream->pipewire_stream_listener,
&pipewire_stream_events, audio_output_stream);
result = pw_stream_connect (audio_output_stream->pipewire_stream,
PW_DIRECTION_INPUT, target_node_id,
PW_STREAM_FLAG_RT_PROCESS |
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_INACTIVE |
PW_STREAM_FLAG_MAP_BUFFERS,
params, 1);
if (result < 0)
{
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (-result),
strerror (-result));
return FALSE;
}
return TRUE;
}
GrdRdpAudioOutputStream *
grd_rdp_audio_output_stream_new (GrdRdpDvcAudioPlayback *audio_playback,
struct pw_core *pipewire_core,
struct pw_registry *pipewire_registry,
uint32_t target_node_id,
uint32_t n_samples_per_sec,
uint32_t n_channels,
uint32_t *position,
GError **error)
{
g_autoptr (GrdRdpAudioOutputStream) audio_output_stream = NULL;
audio_output_stream = g_object_new (GRD_TYPE_RDP_AUDIO_OUTPUT_STREAM, NULL);
audio_output_stream->audio_playback = audio_playback;
audio_output_stream->target_node_id = target_node_id;
audio_output_stream->pipewire_node = pw_registry_bind (pipewire_registry,
target_node_id,
PW_TYPE_INTERFACE_Node,
PW_VERSION_NODE, 0);
if (!audio_output_stream->pipewire_node)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to bind PipeWire node");
return NULL;
}
pw_node_add_listener (audio_output_stream->pipewire_node,
&audio_output_stream->pipewire_node_listener,
&pipewire_node_events, audio_output_stream);
if (!connect_to_stream (audio_output_stream, pipewire_core, target_node_id,
n_samples_per_sec, n_channels, position, error))
return NULL;
return g_steal_pointer (&audio_output_stream);
}
static void
grd_rdp_audio_output_stream_dispose (GObject *object)
{
GrdRdpAudioOutputStream *audio_output_stream =
GRD_RDP_AUDIO_OUTPUT_STREAM (object);
if (audio_output_stream->pipewire_stream)
{
pw_stream_destroy (audio_output_stream->pipewire_stream);
audio_output_stream->pipewire_stream = NULL;
}
if (audio_output_stream->pipewire_node)
{
spa_hook_remove (&audio_output_stream->pipewire_node_listener);
pw_proxy_destroy ((struct pw_proxy *) audio_output_stream->pipewire_node);
audio_output_stream->pipewire_node = NULL;
}
G_OBJECT_CLASS (grd_rdp_audio_output_stream_parent_class)->dispose (object);
}
static void
grd_rdp_audio_output_stream_finalize (GObject *object)
{
GrdRdpAudioOutputStream *audio_output_stream =
GRD_RDP_AUDIO_OUTPUT_STREAM (object);
g_mutex_clear (&audio_output_stream->volume_mutex);
G_OBJECT_CLASS (grd_rdp_audio_output_stream_parent_class)->finalize (object);
}
static void
grd_rdp_audio_output_stream_init (GrdRdpAudioOutputStream *audio_output_stream)
{
g_mutex_init (&audio_output_stream->volume_mutex);
}
static void
grd_rdp_audio_output_stream_class_init (GrdRdpAudioOutputStreamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_audio_output_stream_dispose;
object_class->finalize = grd_rdp_audio_output_stream_finalize;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 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.
*/
#pragma once
#include <glib-object.h>
#include <spa/param/audio/raw.h>
#include <pipewire/pipewire.h>
#include "grd-types.h"
#define GRD_TYPE_RDP_AUDIO_OUTPUT_STREAM (grd_rdp_audio_output_stream_get_type ())
G_DECLARE_FINAL_TYPE (GrdRdpAudioOutputStream, grd_rdp_audio_output_stream,
GRD, RDP_AUDIO_OUTPUT_STREAM, GObject)
struct _GrdRdpAudioVolumeData
{
gboolean audio_muted;
float volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_volumes;
};
GrdRdpAudioOutputStream *grd_rdp_audio_output_stream_new (GrdRdpDvcAudioPlayback *audio_playback,
struct pw_core *pipewire_core,
struct pw_registry *pipewire_registry,
uint32_t target_node_id,
uint32_t n_samples_per_sec,
uint32_t n_channels,
uint32_t *position,
GError **error);
void grd_rdp_audio_output_stream_set_active (GrdRdpAudioOutputStream *audio_output_stream,
gboolean active);
void grd_rdp_audio_output_stream_get_volume_data (GrdRdpAudioOutputStream *audio_output_stream,
GrdRdpAudioVolumeData *volume_data);

38
grd-rdp-buffer-info.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <stdint.h>
typedef enum
{
GRD_RDP_BUFFER_TYPE_NONE,
GRD_RDP_BUFFER_TYPE_DMA_BUF,
GRD_RDP_BUFFER_TYPE_MEM_FD,
} GrdRdpBufferType;
struct _GrdRdpBufferInfo
{
GrdRdpBufferType buffer_type;
uint32_t drm_format;
uint64_t drm_format_modifier;
gboolean has_vk_image;
};

394
grd-rdp-buffer-pool.c Normal file
View File

@@ -0,0 +1,394 @@
/*
* Copyright (C) 2022 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-buffer-pool.h"
#include "grd-context.h"
#include "grd-egl-thread.h"
#include "grd-rdp-legacy-buffer.h"
#include "grd-rdp-renderer.h"
#include "grd-rdp-server.h"
#include "grd-rdp-surface.h"
#include "grd-session-rdp.h"
#include "grd-utils.h"
typedef struct _BufferInfo
{
gboolean buffer_taken;
} BufferInfo;
struct _GrdRdpBufferPool
{
GObject parent;
GrdRdpSurface *rdp_surface;
uint32_t buffer_height;
uint32_t buffer_stride;
GSource *resize_pool_source;
uint32_t minimum_pool_size;
GSource *unmap_source;
GMutex pool_mutex;
GHashTable *buffer_table;
uint32_t buffers_taken;
};
G_DEFINE_TYPE (GrdRdpBufferPool, grd_rdp_buffer_pool, G_TYPE_OBJECT)
GrdRdpSurface *
grd_rdp_buffer_pool_get_surface (GrdRdpBufferPool *buffer_pool)
{
return buffer_pool->rdp_surface;
}
static gboolean
add_buffer_to_pool (GrdRdpBufferPool *buffer_pool,
gboolean preallocate_on_gpu)
{
GrdRdpLegacyBuffer *buffer;
BufferInfo *buffer_info;
buffer = grd_rdp_legacy_buffer_new (buffer_pool,
buffer_pool->buffer_height,
buffer_pool->buffer_stride,
preallocate_on_gpu);
if (!buffer)
return FALSE;
buffer_info = g_new0 (BufferInfo, 1);
g_hash_table_insert (buffer_pool->buffer_table, buffer, buffer_info);
return TRUE;
}
static gboolean
fill_buffer_pool (GrdRdpBufferPool *buffer_pool)
{
uint32_t minimum_size = buffer_pool->minimum_pool_size;
while (g_hash_table_size (buffer_pool->buffer_table) < minimum_size)
{
if (!add_buffer_to_pool (buffer_pool, TRUE))
return FALSE;
}
return TRUE;
}
gboolean
grd_rdp_buffer_pool_resize_buffers (GrdRdpBufferPool *buffer_pool,
uint32_t buffer_height,
uint32_t buffer_stride)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&buffer_pool->pool_mutex);
g_assert (buffer_pool->buffers_taken == 0);
buffer_pool->buffer_height = buffer_height;
buffer_pool->buffer_stride = buffer_stride;
g_hash_table_remove_all (buffer_pool->buffer_table);
if (!fill_buffer_pool (buffer_pool))
return FALSE;
return TRUE;
}
static gboolean
buffer_has_mapped_data (GrdRdpLegacyBuffer *buffer)
{
if (grd_rdp_legacy_buffer_get_mapped_cuda_pointer (buffer))
return TRUE;
return FALSE;
}
GrdRdpLegacyBuffer *
grd_rdp_buffer_pool_acquire (GrdRdpBufferPool *buffer_pool)
{
g_autoptr (GMutexLocker) locker = NULL;
GHashTableIter iter;
GrdRdpLegacyBuffer *buffer;
BufferInfo *buffer_info;
gboolean buffer_found = FALSE;
g_assert (buffer_pool->buffer_height > 0);
g_assert (buffer_pool->buffer_stride > 0);
locker = g_mutex_locker_new (&buffer_pool->pool_mutex);
if (g_hash_table_size (buffer_pool->buffer_table) <= buffer_pool->buffers_taken &&
!add_buffer_to_pool (buffer_pool, FALSE))
return NULL;
g_hash_table_iter_init (&iter, buffer_pool->buffer_table);
while (g_hash_table_iter_next (&iter, (gpointer *) &buffer,
(gpointer *) &buffer_info))
{
if (!buffer_info->buffer_taken && !buffer_has_mapped_data (buffer))
{
buffer_found = TRUE;
break;
}
}
if (!buffer_found)
{
g_hash_table_iter_init (&iter, buffer_pool->buffer_table);
while (g_hash_table_iter_next (&iter, (gpointer *) &buffer,
(gpointer *) &buffer_info))
{
if (!buffer_info->buffer_taken)
break;
}
}
g_assert (buffer);
buffer_info->buffer_taken = TRUE;
++buffer_pool->buffers_taken;
return buffer;
}
static gboolean
should_resize_buffer_pool (GrdRdpBufferPool *buffer_pool)
{
uint32_t buffers_taken = buffer_pool->buffers_taken;
uint32_t minimum_size = buffer_pool->minimum_pool_size;
uint32_t current_pool_size;
current_pool_size = g_hash_table_size (buffer_pool->buffer_table);
if (current_pool_size > minimum_size &&
current_pool_size > buffers_taken)
return TRUE;
return FALSE;
}
void
grd_rdp_buffer_pool_release_buffer (GrdRdpBufferPool *buffer_pool,
GrdRdpLegacyBuffer *buffer)
{
BufferInfo *buffer_info;
gboolean queue_pool_resize;
gboolean queue_unmap;
g_mutex_lock (&buffer_pool->pool_mutex);
if (!g_hash_table_lookup_extended (buffer_pool->buffer_table, buffer,
NULL, (gpointer *) &buffer_info))
g_assert_not_reached ();
g_assert (buffer_info->buffer_taken);
buffer_info->buffer_taken = FALSE;
--buffer_pool->buffers_taken;
queue_pool_resize = should_resize_buffer_pool (buffer_pool);
queue_unmap = buffer_has_mapped_data (buffer);
g_mutex_unlock (&buffer_pool->pool_mutex);
if (queue_pool_resize)
g_source_set_ready_time (buffer_pool->resize_pool_source, 0);
if (queue_unmap)
g_source_set_ready_time (buffer_pool->unmap_source, 0);
}
static gboolean
resize_buffer_pool (gpointer user_data)
{
GrdRdpBufferPool *buffer_pool = user_data;
GHashTableIter iter;
BufferInfo *buffer_info;
g_mutex_lock (&buffer_pool->pool_mutex);
g_hash_table_iter_init (&iter, buffer_pool->buffer_table);
while (should_resize_buffer_pool (buffer_pool) &&
g_hash_table_iter_next (&iter, NULL, (gpointer *) &buffer_info))
{
if (!buffer_info->buffer_taken)
g_hash_table_iter_remove (&iter);
}
g_mutex_unlock (&buffer_pool->pool_mutex);
return G_SOURCE_CONTINUE;
}
static gboolean
unmap_untaken_buffers (gpointer user_data)
{
GrdRdpBufferPool *buffer_pool = user_data;
GHashTableIter iter;
GrdRdpLegacyBuffer *buffer;
BufferInfo *buffer_info;
g_mutex_lock (&buffer_pool->pool_mutex);
g_hash_table_iter_init (&iter, buffer_pool->buffer_table);
while (g_hash_table_iter_next (&iter, (gpointer *) &buffer,
(gpointer *) &buffer_info))
{
if (!buffer_info->buffer_taken)
grd_rdp_legacy_buffer_queue_resource_unmap (buffer);
}
g_mutex_unlock (&buffer_pool->pool_mutex);
return G_SOURCE_CONTINUE;
}
static gboolean
buffer_pool_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_source_set_ready_time (source, -1);
return callback (user_data);
}
static GSourceFuncs buffer_pool_source_funcs =
{
.dispatch = buffer_pool_source_dispatch,
};
GrdRdpBufferPool *
grd_rdp_buffer_pool_new (GrdRdpSurface *rdp_surface,
uint32_t minimum_size)
{
g_autoptr (GrdRdpBufferPool) buffer_pool = NULL;
buffer_pool = g_object_new (GRD_TYPE_RDP_BUFFER_POOL, NULL);
buffer_pool->rdp_surface = rdp_surface;
buffer_pool->minimum_pool_size = minimum_size;
buffer_pool->resize_pool_source = g_source_new (&buffer_pool_source_funcs,
sizeof (GSource));
g_source_set_callback (buffer_pool->resize_pool_source,
resize_buffer_pool, buffer_pool, NULL);
g_source_set_ready_time (buffer_pool->resize_pool_source, -1);
g_source_attach (buffer_pool->resize_pool_source, NULL);
buffer_pool->unmap_source = g_source_new (&buffer_pool_source_funcs,
sizeof (GSource));
g_source_set_callback (buffer_pool->unmap_source,
unmap_untaken_buffers, buffer_pool, NULL);
g_source_set_priority (buffer_pool->unmap_source, G_PRIORITY_LOW);
g_source_set_ready_time (buffer_pool->unmap_source, -1);
g_source_attach (buffer_pool->unmap_source, NULL);
return g_steal_pointer (&buffer_pool);
}
static void
on_sync_complete (gboolean success,
gpointer user_data)
{
GrdSyncPoint *sync_point = user_data;
grd_sync_point_complete (sync_point, success);
}
static void
sync_egl_thread (GrdRdpBufferPool *buffer_pool)
{
GrdRdpRenderer *renderer = buffer_pool->rdp_surface->renderer;
GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (renderer);
GrdRdpServer *rdp_server = grd_session_rdp_get_server (session_rdp);
GrdContext *context = grd_rdp_server_get_context (rdp_server);
GrdEglThread *egl_thread = grd_context_get_egl_thread (context);
GrdSyncPoint sync_point = {};
grd_sync_point_init (&sync_point);
grd_egl_thread_sync (egl_thread, on_sync_complete,
&sync_point, NULL);
grd_sync_point_wait_for_completion (&sync_point);
grd_sync_point_clear (&sync_point);
}
static void
grd_rdp_buffer_pool_dispose (GObject *object)
{
GrdRdpBufferPool *buffer_pool = GRD_RDP_BUFFER_POOL (object);
if (buffer_pool->unmap_source)
{
g_source_destroy (buffer_pool->unmap_source);
g_clear_pointer (&buffer_pool->unmap_source, g_source_unref);
}
if (buffer_pool->resize_pool_source)
{
g_source_destroy (buffer_pool->resize_pool_source);
g_clear_pointer (&buffer_pool->resize_pool_source, g_source_unref);
}
g_assert (buffer_pool->buffers_taken == 0);
G_OBJECT_CLASS (grd_rdp_buffer_pool_parent_class)->dispose (object);
}
static void
grd_rdp_buffer_pool_finalize (GObject *object)
{
GrdRdpBufferPool *buffer_pool = GRD_RDP_BUFFER_POOL (object);
GrdRdpRenderer *renderer = buffer_pool->rdp_surface->renderer;
GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (renderer);
GrdRdpServer *rdp_server = grd_session_rdp_get_server (session_rdp);
GrdContext *context = grd_rdp_server_get_context (rdp_server);
GrdEglThread *egl_thread = grd_context_get_egl_thread (context);
g_mutex_clear (&buffer_pool->pool_mutex);
g_clear_pointer (&buffer_pool->buffer_table, g_hash_table_unref);
/*
* All buffers need to be destroyed, before the pool is freed to avoid use
* after free by the EGL thread, when the RDP server is shut down and with it
* the GrdHwAccelNvidia instance
*/
if (egl_thread)
sync_egl_thread (buffer_pool);
G_OBJECT_CLASS (grd_rdp_buffer_pool_parent_class)->finalize (object);
}
static void
grd_rdp_buffer_pool_init (GrdRdpBufferPool *buffer_pool)
{
buffer_pool->buffer_table =
g_hash_table_new_full (NULL, NULL,
(GDestroyNotify) grd_rdp_legacy_buffer_free,
g_free);
g_mutex_init (&buffer_pool->pool_mutex);
}
static void
grd_rdp_buffer_pool_class_init (GrdRdpBufferPoolClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_buffer_pool_dispose;
object_class->finalize = grd_rdp_buffer_pool_finalize;
}

44
grd-rdp-buffer-pool.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 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.
*/
#pragma once
#include <ffnvcodec/dynlink_cuda.h>
#include <glib-object.h>
#include <stdint.h>
#include "grd-types.h"
#define GRD_TYPE_RDP_BUFFER_POOL (grd_rdp_buffer_pool_get_type ())
G_DECLARE_FINAL_TYPE (GrdRdpBufferPool, grd_rdp_buffer_pool,
GRD, RDP_BUFFER_POOL, GObject)
GrdRdpBufferPool *grd_rdp_buffer_pool_new (GrdRdpSurface *rdp_surface,
uint32_t minimum_size);
GrdRdpSurface *grd_rdp_buffer_pool_get_surface (GrdRdpBufferPool *buffer_pool);
gboolean grd_rdp_buffer_pool_resize_buffers (GrdRdpBufferPool *buffer_pool,
uint32_t buffer_height,
uint32_t buffer_stride);
GrdRdpLegacyBuffer *grd_rdp_buffer_pool_acquire (GrdRdpBufferPool *buffer_pool);
void grd_rdp_buffer_pool_release_buffer (GrdRdpBufferPool *buffer_pool,
GrdRdpLegacyBuffer *buffer);

172
grd-rdp-buffer.c Normal file
View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2024 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-buffer.h"
#include <drm_fourcc.h>
#include "grd-rdp-buffer-info.h"
#include "grd-rdp-pw-buffer.h"
#include "grd-rdp-surface.h"
#include "grd-vk-utils.h"
struct _GrdRdpBuffer
{
GObject parent;
GrdRdpPwBuffer *rdp_pw_buffer;
GrdRdpBufferInfo *rdp_buffer_info;
GrdVkImage *dma_buf_image;
gboolean marked_for_removal;
};
G_DEFINE_TYPE (GrdRdpBuffer, grd_rdp_buffer, G_TYPE_OBJECT)
GrdRdpPwBuffer *
grd_rdp_buffer_get_rdp_pw_buffer (GrdRdpBuffer *rdp_buffer)
{
return rdp_buffer->rdp_pw_buffer;
}
const GrdRdpBufferInfo *
grd_rdp_buffer_get_rdp_buffer_info (GrdRdpBuffer *rdp_buffer)
{
return rdp_buffer->rdp_buffer_info;
}
GrdVkImage *
grd_rdp_buffer_get_dma_buf_image (GrdRdpBuffer *rdp_buffer)
{
return rdp_buffer->dma_buf_image;
}
gboolean
grd_rdp_buffer_is_marked_for_removal (GrdRdpBuffer *rdp_buffer)
{
return rdp_buffer->marked_for_removal;
}
void
grd_rdp_buffer_mark_for_removal (GrdRdpBuffer *rdp_buffer)
{
rdp_buffer->marked_for_removal = TRUE;
}
static gboolean
import_dma_buf_image (GrdRdpBuffer *rdp_buffer,
GrdRdpPwBuffer *rdp_pw_buffer,
GrdRdpBufferInfo *rdp_buffer_info,
GrdRdpSurface *rdp_surface,
GrdVkDevice *vk_device,
GError **error)
{
uint32_t surface_width = grd_rdp_surface_get_width (rdp_surface);
uint32_t surface_height = grd_rdp_surface_get_height (rdp_surface);
const GrdRdpPwBufferDmaBufInfo *dma_buf_info =
grd_rdp_pw_buffer_get_dma_buf_info (rdp_pw_buffer);
VkFormat vk_format = VK_FORMAT_UNDEFINED;
if (!grd_vk_get_vk_format_from_drm_format (rdp_buffer_info->drm_format,
&vk_format, error))
return FALSE;
rdp_buffer->dma_buf_image =
grd_vk_dma_buf_image_new (vk_device, vk_format,
surface_width, surface_height,
VK_IMAGE_USAGE_SAMPLED_BIT,
dma_buf_info->fd,
dma_buf_info->offset,
dma_buf_info->stride,
rdp_buffer_info->drm_format_modifier,
error);
if (!rdp_buffer->dma_buf_image)
return FALSE;
rdp_buffer_info->has_vk_image = TRUE;
return TRUE;
}
GrdRdpBuffer *
grd_rdp_buffer_new (GrdRdpPwBuffer *rdp_pw_buffer,
GrdRdpBufferInfo *rdp_buffer_info,
GrdRdpSurface *rdp_surface,
GrdVkDevice *vk_device,
GError **error)
{
g_autoptr (GrdRdpBuffer) rdp_buffer = NULL;
GrdRdpBufferType buffer_type;
rdp_buffer = g_object_new (GRD_TYPE_RDP_BUFFER, NULL);
rdp_buffer->rdp_pw_buffer = rdp_pw_buffer;
buffer_type = grd_rdp_pw_buffer_get_buffer_type (rdp_pw_buffer);
if (buffer_type == GRD_RDP_BUFFER_TYPE_DMA_BUF &&
vk_device &&
rdp_buffer_info->drm_format_modifier != DRM_FORMAT_MOD_INVALID)
{
g_autoptr (GError) local_error = NULL;
if (!import_dma_buf_image (rdp_buffer, rdp_pw_buffer, rdp_buffer_info,
rdp_surface, vk_device, &local_error))
{
if (rdp_buffer_info->has_vk_image)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
g_debug ("[RDP] Could not import dma-buf image: %s",
local_error->message);
}
}
rdp_buffer->rdp_buffer_info =
g_memdup2 (rdp_buffer_info, sizeof (GrdRdpBufferInfo));
return g_steal_pointer (&rdp_buffer);
}
static void
grd_rdp_buffer_dispose (GObject *object)
{
GrdRdpBuffer *rdp_buffer = GRD_RDP_BUFFER (object);
g_clear_object (&rdp_buffer->dma_buf_image);
g_clear_pointer (&rdp_buffer->rdp_buffer_info, g_free);
G_OBJECT_CLASS (grd_rdp_buffer_parent_class)->dispose (object);
}
static void
grd_rdp_buffer_init (GrdRdpBuffer *rdp_buffer)
{
}
static void
grd_rdp_buffer_class_init (GrdRdpBufferClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_buffer_dispose;
}

44
grd-rdp-buffer.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <glib-object.h>
#include "grd-types.h"
#define GRD_TYPE_RDP_BUFFER (grd_rdp_buffer_get_type ())
G_DECLARE_FINAL_TYPE (GrdRdpBuffer, grd_rdp_buffer,
GRD, RDP_BUFFER, GObject)
GrdRdpBuffer *grd_rdp_buffer_new (GrdRdpPwBuffer *rdp_pw_buffer,
GrdRdpBufferInfo *rdp_buffer_info,
GrdRdpSurface *rdp_surface,
GrdVkDevice *vk_device,
GError **error);
GrdRdpPwBuffer *grd_rdp_buffer_get_rdp_pw_buffer (GrdRdpBuffer *rdp_buffer);
const GrdRdpBufferInfo *grd_rdp_buffer_get_rdp_buffer_info (GrdRdpBuffer *rdp_buffer);
GrdVkImage *grd_rdp_buffer_get_dma_buf_image (GrdRdpBuffer *rdp_buffer);
gboolean grd_rdp_buffer_is_marked_for_removal (GrdRdpBuffer *rdp_buffer);
void grd_rdp_buffer_mark_for_removal (GrdRdpBuffer *rdp_buffer);

1079
grd-rdp-camera-stream.c Normal file

File diff suppressed because it is too large Load Diff

53
grd-rdp-camera-stream.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <glib-object.h>
#include <pipewire/pipewire.h>
#include "grd-types.h"
#define GRD_TYPE_RDP_CAMERA_STREAM (grd_rdp_camera_stream_get_type ())
G_DECLARE_FINAL_TYPE (GrdRdpCameraStream, grd_rdp_camera_stream,
GRD, RDP_CAMERA_STREAM, GObject)
GrdRdpCameraStream *grd_rdp_camera_stream_new (GrdRdpDvcCameraDevice *device,
GMainContext *camera_context,
struct pw_core *pipewire_core,
const char *device_name,
uint8_t stream_index,
GList *media_type_descriptions,
GError **error);
uint8_t grd_rdp_camera_stream_get_stream_index (GrdRdpCameraStream *camera_stream);
gboolean grd_rdp_camera_stream_notify_stream_started (GrdRdpCameraStream *camera_stream,
uint32_t run_sequence);
void grd_rdp_camera_stream_inhibit_camera_loop (GrdRdpCameraStream *camera_stream);
void grd_rdp_camera_stream_uninhibit_camera_loop (GrdRdpCameraStream *camera_stream);
gboolean grd_rdp_camera_stream_announce_new_sample (GrdRdpCameraStream *camera_stream,
GrdSampleBuffer *sample_buffer);
void grd_rdp_camera_stream_submit_sample (GrdRdpCameraStream *camera_stream,
GrdSampleBuffer *sample_buffer,
gboolean success);

View File

@@ -0,0 +1,643 @@
/*
* 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-connect-time-autodetection.h"
#define CT_BW_MAX_TOLERABLE_RESPONSE_LATENCY_US (400 * 1000)
#define CT_BW_MAX_TOLERABLE_TIME_DELTA_MS 100
#define CT_N_PINGS 10
#define CT_PING_INTERVAL_MS 10
#define BW_MEASURE_SEQUENCE_NUMBER 0
#define CT_NON_RTT_SEQUENCE_NUMBER 0
typedef enum
{
CT_AUTODETECT_STATE_NONE,
CT_AUTODETECT_STATE_MEASURE_BW_1,
CT_AUTODETECT_STATE_MEASURE_BW_2,
CT_AUTODETECT_STATE_MEASURE_BW_3,
CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1,
CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2,
CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3,
CT_AUTODETECT_STATE_START_RTT_DETECTION,
CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE,
CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT,
CT_AUTODETECT_STATE_COMPLETE,
} CtAutodetectState;
struct _GrdRdpConnectTimeAutodetection
{
GObject parent;
GrdRdpNetworkAutodetection *network_autodetection;
rdpAutoDetect *rdp_autodetect;
gboolean protocol_stopped;
GMutex ct_autodetection_mutex;
CtAutodetectState state;
GSource *sync_source;
GCond net_char_sync_cond;
GMutex net_char_sync_mutex;
gboolean pending_net_char_sync;
GSource *ping_source;
uint32_t pending_pings;
gboolean pending_last_sequence_number;
uint16_t last_sequence_number;
int64_t base_round_trip_time_us;
int64_t avg_round_trip_time_us;
int64_t bw_measure_time_us;
uint32_t last_time_delta_ms;
uint32_t last_byte_count;
};
G_DEFINE_TYPE (GrdRdpConnectTimeAutodetection, grd_rdp_connect_time_autodetection,
G_TYPE_OBJECT)
gboolean
grd_rdp_connect_time_autodetection_is_complete (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
return ct_autodetection->state == CT_AUTODETECT_STATE_COMPLETE;
}
static void
determine_bw_measure_payloads (GrdRdpConnectTimeAutodetection *ct_autodetection,
uint16_t *payload_lengths,
uint32_t *n_payloads)
{
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_MEASURE_BW_1:
*payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
*n_payloads = 1;
break;
case CT_AUTODETECT_STATE_MEASURE_BW_2:
*payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
*n_payloads = 4;
break;
case CT_AUTODETECT_STATE_MEASURE_BW_3:
*payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
*n_payloads = 16;
break;
default:
g_assert_not_reached ();
}
}
static CtAutodetectState
get_next_bw_measure_state (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_MEASURE_BW_1:
return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1;
case CT_AUTODETECT_STATE_MEASURE_BW_2:
return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2;
case CT_AUTODETECT_STATE_MEASURE_BW_3:
return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
return CT_AUTODETECT_STATE_MEASURE_BW_2;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
return CT_AUTODETECT_STATE_MEASURE_BW_3;
default:
g_assert_not_reached ();
}
return CT_AUTODETECT_STATE_NONE;
}
static void
measure_connect_time_bandwidth (GrdRdpConnectTimeAutodetection *ct_autodetection,
uint16_t payload_lengths,
uint32_t n_payloads)
{
rdpAutoDetect *rdp_autodetect = ct_autodetection->rdp_autodetect;
uint32_t i;
g_assert (payload_lengths > 0);
g_assert (n_payloads >= 1);
rdp_autodetect->BandwidthMeasureStart (rdp_autodetect, RDP_TRANSPORT_TCP,
BW_MEASURE_SEQUENCE_NUMBER);
for (i = 0; i < n_payloads - 1; ++i)
{
rdp_autodetect->BandwidthMeasurePayload (rdp_autodetect,
RDP_TRANSPORT_TCP,
BW_MEASURE_SEQUENCE_NUMBER,
payload_lengths);
}
rdp_autodetect->BandwidthMeasureStop (rdp_autodetect, RDP_TRANSPORT_TCP,
BW_MEASURE_SEQUENCE_NUMBER,
payload_lengths);
}
static const char *
ct_autodetect_state_to_string (CtAutodetectState state)
{
switch (state)
{
case CT_AUTODETECT_STATE_NONE:
return "NONE";
case CT_AUTODETECT_STATE_MEASURE_BW_1:
return "MEASURE_BW_1";
case CT_AUTODETECT_STATE_MEASURE_BW_2:
return "MEASURE_BW_2";
case CT_AUTODETECT_STATE_MEASURE_BW_3:
return "MEASURE_BW_3";
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
return "AWAIT_BW_RESULT_1";
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
return "AWAIT_BW_RESULT_2";
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
return "AWAIT_BW_RESULT_3";
case CT_AUTODETECT_STATE_START_RTT_DETECTION:
return "START_RTT_DETECTION";
case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
return "AWAIT_LAST_RTT_RESPONSE";
case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
return "SEND_NET_CHAR_RESULT";
case CT_AUTODETECT_STATE_COMPLETE:
return "COMPLETE";
}
g_assert_not_reached ();
}
static void
transition_to_state (GrdRdpConnectTimeAutodetection *ct_autodetection,
CtAutodetectState state)
{
g_assert (ct_autodetection->state != state);
g_debug ("[RDP] Connect-Time Autodetect: Updating state from %s to %s",
ct_autodetect_state_to_string (ct_autodetection->state),
ct_autodetect_state_to_string (state));
ct_autodetection->state = state;
}
static void
last_sequence_number_ready (gpointer user_data,
uint16_t sequence_number)
{
GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;
g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
ct_autodetection->last_sequence_number = sequence_number;
ct_autodetection->pending_last_sequence_number = FALSE;
g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
}
static gboolean
emit_ping (gpointer user_data)
{
GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;
GrdRdpNetworkAutodetection *network_autodetection =
ct_autodetection->network_autodetection;
GrdRdpNwAutodetectSequenceNumberReadyCallback sequence_number_ready = NULL;
g_assert (!ct_autodetection->protocol_stopped);
g_assert (ct_autodetection->pending_pings > 0);
--ct_autodetection->pending_pings;
if (ct_autodetection->pending_pings == 0)
{
g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
sequence_number_ready = last_sequence_number_ready;
}
grd_rdp_network_autodetection_emit_ping (network_autodetection,
sequence_number_ready,
ct_autodetection);
if (ct_autodetection->pending_pings == 0)
return G_SOURCE_REMOVE;
return G_SOURCE_CONTINUE;
}
static void
start_ping_emission (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
emit_ping (ct_autodetection);
g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
ct_autodetection->ping_source = g_timeout_source_new (CT_PING_INTERVAL_MS);
g_source_set_callback (ct_autodetection->ping_source, emit_ping,
ct_autodetection, NULL);
g_source_attach (ct_autodetection->ping_source, NULL);
g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
}
static void
notify_network_characteristics_result (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
rdpAutoDetect *rdp_autodetect = ct_autodetection->rdp_autodetect;
rdpNetworkCharacteristicsResult result = {};
uint64_t bit_count;
uint32_t bandwidth_kbits;
bit_count = ((uint64_t) ct_autodetection->last_byte_count) * UINT64_C (8);
bandwidth_kbits = bit_count / MAX (ct_autodetection->last_time_delta_ms, 1);
result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
result.baseRTT = ct_autodetection->base_round_trip_time_us / 1000;
result.averageRTT = ct_autodetection->avg_round_trip_time_us / 1000;
result.bandwidth = bandwidth_kbits;
rdp_autodetect->NetworkCharacteristicsResult (rdp_autodetect,
RDP_TRANSPORT_TCP,
CT_NON_RTT_SEQUENCE_NUMBER,
&result);
}
static FREERDP_AUTODETECT_STATE
detect_network_characteristics (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
CtAutodetectState new_state = CT_AUTODETECT_STATE_NONE;
g_autoptr (GMutexLocker) locker = NULL;
gboolean pending_bw_request = FALSE;
gboolean pending_ping_emission = FALSE;
gboolean pending_net_char_result = FALSE;
uint16_t payload_lengths = 0;
uint32_t n_payloads = 1;
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_NONE:
g_assert_not_reached ();
case CT_AUTODETECT_STATE_MEASURE_BW_1:
case CT_AUTODETECT_STATE_MEASURE_BW_2:
case CT_AUTODETECT_STATE_MEASURE_BW_3:
determine_bw_measure_payloads (ct_autodetection,
&payload_lengths, &n_payloads);
ct_autodetection->bw_measure_time_us = g_get_monotonic_time ();
new_state = get_next_bw_measure_state (ct_autodetection);
pending_bw_request = TRUE;
break;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
return FREERDP_AUTODETECT_STATE_REQUEST;
case CT_AUTODETECT_STATE_START_RTT_DETECTION:
g_assert (!ct_autodetection->ping_source);
g_assert (ct_autodetection->pending_pings >= 2);
new_state = CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE;
pending_ping_emission = TRUE;
break;
case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
return FREERDP_AUTODETECT_STATE_REQUEST;
case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
new_state = CT_AUTODETECT_STATE_COMPLETE;
pending_net_char_result = TRUE;
break;
case CT_AUTODETECT_STATE_COMPLETE:
return FREERDP_AUTODETECT_STATE_COMPLETE;
}
g_assert (new_state != CT_AUTODETECT_STATE_NONE);
transition_to_state (ct_autodetection, new_state);
g_clear_pointer (&locker, g_mutex_locker_free);
if (pending_bw_request)
{
measure_connect_time_bandwidth (ct_autodetection, payload_lengths,
n_payloads);
}
if (pending_ping_emission)
start_ping_emission (ct_autodetection);
if (pending_net_char_result)
{
notify_network_characteristics_result (ct_autodetection);
return FREERDP_AUTODETECT_STATE_COMPLETE;
}
return FREERDP_AUTODETECT_STATE_REQUEST;
}
void
grd_rdp_connect_time_autodetection_start_detection (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
g_assert (ct_autodetection->state == CT_AUTODETECT_STATE_NONE);
transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_MEASURE_BW_1);
g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
detect_network_characteristics (ct_autodetection);
}
void
grd_rdp_connect_time_autodetection_invoke_shutdown (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
ct_autodetection->protocol_stopped = TRUE;
g_cond_signal (&ct_autodetection->net_char_sync_cond);
g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);
}
static uint32_t
get_bw_measure_phase (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
return 1;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
return 2;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
return 3;
default:
g_assert_not_reached ();
}
return 0;
}
BOOL
grd_rdp_connect_time_autodetection_notify_bw_measure_results (GrdRdpConnectTimeAutodetection *ct_autodetection,
uint32_t time_delta_ms,
uint32_t byte_count)
{
CtAutodetectState new_state = CT_AUTODETECT_STATE_NONE;
g_autoptr (GMutexLocker) locker = NULL;
int64_t current_time_us;
int64_t response_latency_us;
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
current_time_us = g_get_monotonic_time ();
response_latency_us = current_time_us - ct_autodetection->bw_measure_time_us;
if (response_latency_us < CT_BW_MAX_TOLERABLE_RESPONSE_LATENCY_US &&
time_delta_ms < CT_BW_MAX_TOLERABLE_TIME_DELTA_MS)
new_state = get_next_bw_measure_state (ct_autodetection);
else
new_state = CT_AUTODETECT_STATE_START_RTT_DETECTION;
break;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
new_state = CT_AUTODETECT_STATE_START_RTT_DETECTION;
break;
default:
g_warning ("[RDP] Connect-Time Autodetect: Received stray Bandwidth "
"Measure Results PDU (current state: %s)",
ct_autodetect_state_to_string (ct_autodetection->state));
return FALSE;
}
g_debug ("[RDP] Connect-Time Autodetect: BW Measure Phase %u: "
"time delta: %ums, byte count: %u => %uKB/s",
get_bw_measure_phase (ct_autodetection), time_delta_ms, byte_count,
byte_count / MAX (time_delta_ms, 1));
ct_autodetection->last_time_delta_ms = time_delta_ms;
ct_autodetection->last_byte_count = byte_count;
g_assert (new_state != CT_AUTODETECT_STATE_NONE);
transition_to_state (ct_autodetection, new_state);
return TRUE;
}
void
grd_rdp_connect_time_autodetection_notify_rtt_measure_response (GrdRdpConnectTimeAutodetection *ct_autodetection,
uint16_t sequence_number,
int64_t base_round_trip_time_us,
int64_t avg_round_trip_time_us)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
if (ct_autodetection->pending_last_sequence_number ||
ct_autodetection->last_sequence_number != sequence_number)
return;
g_assert (ct_autodetection->state == CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE);
g_debug ("[RDP] Connect-Time Autodetect: base RTT: %lims, average RTT: %lims",
base_round_trip_time_us / 1000, avg_round_trip_time_us / 1000);
ct_autodetection->base_round_trip_time_us = base_round_trip_time_us;
ct_autodetection->avg_round_trip_time_us = avg_round_trip_time_us;
transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT);
}
static gboolean
sync_with_main_context (gpointer user_data)
{
GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;
g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
if (ct_autodetection->ping_source)
{
g_source_destroy (ct_autodetection->ping_source);
g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
}
g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
g_clear_pointer (&ct_autodetection->sync_source, g_source_unref);
ct_autodetection->pending_net_char_sync = FALSE;
g_cond_signal (&ct_autodetection->net_char_sync_cond);
g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);
return G_SOURCE_REMOVE;
}
static void
sync_ct_autodetect_handling (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
ct_autodetection->pending_net_char_sync = TRUE;
ct_autodetection->sync_source = g_idle_source_new ();
g_source_set_callback (ct_autodetection->sync_source, sync_with_main_context,
ct_autodetection, NULL);
g_source_attach (ct_autodetection->sync_source, NULL);
while (!ct_autodetection->protocol_stopped &&
ct_autodetection->pending_net_char_sync)
{
g_cond_wait (&ct_autodetection->net_char_sync_cond,
&ct_autodetection->net_char_sync_mutex);
}
g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);
}
static BOOL
autodetect_network_characteristics_sync (rdpAutoDetect *rdp_autodetect,
RDP_TRANSPORT_TYPE transport_type,
uint16_t sequenceNumber,
uint32_t bandwidth,
uint32_t rtt)
{
GrdRdpNetworkAutodetection *network_autodetection = rdp_autodetect->custom;
GrdRdpConnectTimeAutodetection *ct_autodetection =
grd_rdp_network_autodetection_get_ct_handler (network_autodetection);
g_autoptr (GMutexLocker) locker = NULL;
gboolean pending_sync = FALSE;
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
switch (ct_autodetection->state)
{
case CT_AUTODETECT_STATE_NONE:
case CT_AUTODETECT_STATE_MEASURE_BW_1:
case CT_AUTODETECT_STATE_MEASURE_BW_2:
case CT_AUTODETECT_STATE_MEASURE_BW_3:
g_assert_not_reached ();
break;
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
g_assert (!pending_sync);
break;
case CT_AUTODETECT_STATE_START_RTT_DETECTION:
g_assert_not_reached ();
break;
case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
pending_sync = TRUE;
break;
case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
g_assert_not_reached ();
break;
case CT_AUTODETECT_STATE_COMPLETE:
g_warning ("[RDP] Connect-Time Autodetect: Received stray Network "
"Characteristics Sync PDU (current state: %s). Ignoring PDU...",
ct_autodetect_state_to_string (ct_autodetection->state));
return TRUE;
}
g_clear_pointer (&locker, g_mutex_locker_free);
if (pending_sync)
sync_ct_autodetect_handling (ct_autodetection);
g_debug ("[RDP] Connect-Time Autodetect: Received Sync PDU: "
"RTT: %ums, bandwidth: %uKBit/s", rtt, bandwidth);
locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_COMPLETE);
return TRUE;
}
static FREERDP_AUTODETECT_STATE
autodetect_on_connect_time_autodetect_progress (rdpAutoDetect *rdp_autodetect)
{
GrdRdpNetworkAutodetection *network_autodetection = rdp_autodetect->custom;
GrdRdpConnectTimeAutodetection *ct_autodetection =
grd_rdp_network_autodetection_get_ct_handler (network_autodetection);
return detect_network_characteristics (ct_autodetection);
}
GrdRdpConnectTimeAutodetection *
grd_rdp_connect_time_autodetection_new (GrdRdpNetworkAutodetection *network_autodetection,
rdpAutoDetect *rdp_autodetect)
{
GrdRdpConnectTimeAutodetection *ct_autodetection;
ct_autodetection = g_object_new (GRD_TYPE_RDP_CONNECT_TIME_AUTODETECTION, NULL);
ct_autodetection->network_autodetection = network_autodetection;
ct_autodetection->rdp_autodetect = rdp_autodetect;
rdp_autodetect->NetworkCharacteristicsSync = autodetect_network_characteristics_sync;
rdp_autodetect->OnConnectTimeAutoDetectProgress = autodetect_on_connect_time_autodetect_progress;
return ct_autodetection;
}
static void
grd_rdp_connect_time_autodetection_dispose (GObject *object)
{
GrdRdpConnectTimeAutodetection *ct_autodetection = GRD_RDP_CONNECT_TIME_AUTODETECTION (object);
g_assert (ct_autodetection->protocol_stopped);
if (ct_autodetection->sync_source)
{
g_source_destroy (ct_autodetection->sync_source);
g_clear_pointer (&ct_autodetection->sync_source, g_source_unref);
}
if (ct_autodetection->ping_source)
{
g_source_destroy (ct_autodetection->ping_source);
g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
}
G_OBJECT_CLASS (grd_rdp_connect_time_autodetection_parent_class)->dispose (object);
}
static void
grd_rdp_connect_time_autodetection_finalize (GObject *object)
{
GrdRdpConnectTimeAutodetection *ct_autodetection = GRD_RDP_CONNECT_TIME_AUTODETECTION (object);
g_cond_clear (&ct_autodetection->net_char_sync_cond);
g_mutex_clear (&ct_autodetection->net_char_sync_mutex);
g_mutex_clear (&ct_autodetection->ct_autodetection_mutex);
G_OBJECT_CLASS (grd_rdp_connect_time_autodetection_parent_class)->finalize (object);
}
static void
grd_rdp_connect_time_autodetection_init (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
ct_autodetection->state = CT_AUTODETECT_STATE_NONE;
ct_autodetection->pending_pings = CT_N_PINGS;
ct_autodetection->pending_last_sequence_number = TRUE;
g_mutex_init (&ct_autodetection->ct_autodetection_mutex);
g_mutex_init (&ct_autodetection->net_char_sync_mutex);
g_cond_init (&ct_autodetection->net_char_sync_cond);
}
static void
grd_rdp_connect_time_autodetection_class_init (GrdRdpConnectTimeAutodetectionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_connect_time_autodetection_dispose;
object_class->finalize = grd_rdp_connect_time_autodetection_finalize;
}

Some files were not shown because too many files have changed in this diff Show More