[gnome-remote-desktop] rdp: Move DVC subscriptions into its own class



commit 813627ab11c804b437dfdfed9102d96263f5edba
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Sat Jul 23 05:54:07 2022 +0200

    rdp: Move DVC subscriptions into its own class
    
    This unclutters the session-rdp class a bit.

 src/grd-rdp-audio-playback.c    |  18 ++-
 src/grd-rdp-audio-playback.h    |   1 +
 src/grd-rdp-display-control.c   |  18 ++-
 src/grd-rdp-display-control.h   |   1 +
 src/grd-rdp-dvc.c               | 330 ++++++++++++++++++++++++++++++++++++++++
 src/grd-rdp-dvc.h               |  45 ++++++
 src/grd-rdp-graphics-pipeline.c |  19 ++-
 src/grd-rdp-graphics-pipeline.h |   1 +
 src/grd-rdp-private.h           |   2 +
 src/grd-rdp-telemetry.c         |  18 ++-
 src/grd-rdp-telemetry.h         |   1 +
 src/grd-session-rdp.c           | 258 ++-----------------------------
 src/grd-session-rdp.h           |  12 --
 src/grd-types.h                 |   1 +
 src/meson.build                 |   2 +
 15 files changed, 442 insertions(+), 285 deletions(-)
---
diff --git a/src/grd-rdp-audio-playback.c b/src/grd-rdp-audio-playback.c
index 7fee68b8..0c222fe4 100644
--- a/src/grd-rdp-audio-playback.c
+++ b/src/grd-rdp-audio-playback.c
@@ -24,6 +24,7 @@
 #include "grd-pipewire-utils.h"
 #include "grd-rdp-audio-output-stream.h"
 #include "grd-rdp-dsp.h"
+#include "grd-rdp-dvc.h"
 #include "grd-session-rdp.h"
 
 #define PROTOCOL_TIMEOUT_MS (10 * 1000)
@@ -68,6 +69,7 @@ struct _GrdRdpAudioPlayback
   gboolean subscribed_status;
 
   GrdSessionRdp *session_rdp;
+  GrdRdpDvc *rdp_dvc;
 
   GMutex protocol_timeout_mutex;
   GSource *channel_teardown_source;
@@ -438,10 +440,10 @@ rdpsnd_channel_id_assigned (RdpsndServerContext *rdpsnd_context,
   audio_playback->channel_id = channel_id;
 
   audio_playback->dvc_subscription_id =
-    grd_session_rdp_subscribe_dvc_creation_status (audio_playback->session_rdp,
-                                                   channel_id,
-                                                   dvc_creation_status,
-                                                   audio_playback);
+    grd_rdp_dvc_subscribe_dvc_creation_status (audio_playback->rdp_dvc,
+                                               channel_id,
+                                               dvc_creation_status,
+                                               audio_playback);
   audio_playback->subscribed_status = TRUE;
 
   return TRUE;
@@ -640,6 +642,7 @@ encode_thread_func (gpointer data)
 
 GrdRdpAudioPlayback *
 grd_rdp_audio_playback_new (GrdSessionRdp *session_rdp,
+                            GrdRdpDvc     *rdp_dvc,
                             HANDLE         vcm,
                             HANDLE         stop_event,
                             rdpContext    *rdp_context)
@@ -656,6 +659,7 @@ grd_rdp_audio_playback_new (GrdSessionRdp *session_rdp,
   audio_playback->rdpsnd_context = rdpsnd_context;
   audio_playback->stop_event = stop_event;
   audio_playback->session_rdp = session_rdp;
+  audio_playback->rdp_dvc = rdp_dvc;
 
   rdpsnd_context->use_dynamic_virtual_channel = TRUE;
   rdpsnd_context->server_formats = server_formats;
@@ -706,9 +710,9 @@ grd_rdp_audio_playback_dispose (GObject *object)
     }
   if (audio_playback->subscribed_status)
     {
-      grd_session_rdp_unsubscribe_dvc_creation_status (audio_playback->session_rdp,
-                                                       audio_playback->channel_id,
-                                                       audio_playback->dvc_subscription_id);
+      grd_rdp_dvc_unsubscribe_dvc_creation_status (audio_playback->rdp_dvc,
+                                                   audio_playback->channel_id,
+                                                   audio_playback->dvc_subscription_id);
       audio_playback->subscribed_status = FALSE;
     }
 
diff --git a/src/grd-rdp-audio-playback.h b/src/grd-rdp-audio-playback.h
index f7ae583c..46fa2ff3 100644
--- a/src/grd-rdp-audio-playback.h
+++ b/src/grd-rdp-audio-playback.h
@@ -30,6 +30,7 @@ G_DECLARE_FINAL_TYPE (GrdRdpAudioPlayback, grd_rdp_audio_playback,
                       GRD, RDP_AUDIO_PLAYBACK, GObject)
 
 GrdRdpAudioPlayback *grd_rdp_audio_playback_new (GrdSessionRdp *session_rdp,
+                                                 GrdRdpDvc     *rdp_dvc,
                                                  HANDLE         vcm,
                                                  HANDLE         stop_event,
                                                  rdpContext    *rdp_context);
diff --git a/src/grd-rdp-display-control.c b/src/grd-rdp-display-control.c
index c5e01ea7..de271791 100644
--- a/src/grd-rdp-display-control.c
+++ b/src/grd-rdp-display-control.c
@@ -21,6 +21,7 @@
 
 #include "grd-rdp-display-control.h"
 
+#include "grd-rdp-dvc.h"
 #include "grd-session-rdp.h"
 
 struct _GrdRdpDisplayControl
@@ -37,6 +38,7 @@ struct _GrdRdpDisplayControl
   gboolean subscribed_status;
 
   GrdSessionRdp *session_rdp;
+  GrdRdpDvc *rdp_dvc;
 
   GSource *channel_teardown_source;
 };
@@ -95,10 +97,10 @@ disp_channel_id_assigned (DispServerContext *disp_context,
   display_control->channel_id = channel_id;
 
   display_control->dvc_subscription_id =
-    grd_session_rdp_subscribe_dvc_creation_status (display_control->session_rdp,
-                                                   channel_id,
-                                                   dvc_creation_status,
-                                                   display_control);
+    grd_rdp_dvc_subscribe_dvc_creation_status (display_control->rdp_dvc,
+                                               channel_id,
+                                               dvc_creation_status,
+                                               display_control);
   display_control->subscribed_status = TRUE;
 
   return TRUE;
@@ -137,6 +139,7 @@ disp_monitor_layout (DispServerContext                        *disp_context,
 
 GrdRdpDisplayControl *
 grd_rdp_display_control_new (GrdSessionRdp *session_rdp,
+                             GrdRdpDvc     *rdp_dvc,
                              HANDLE         vcm,
                              HANDLE         stop_event,
                              uint32_t       max_monitor_count)
@@ -152,6 +155,7 @@ grd_rdp_display_control_new (GrdSessionRdp *session_rdp,
   display_control->disp_context = disp_context;
   display_control->stop_event = stop_event;
   display_control->session_rdp = session_rdp;
+  display_control->rdp_dvc = rdp_dvc;
 
   disp_context->MaxNumMonitors = max_monitor_count;
   disp_context->MaxMonitorAreaFactorA = 8192;
@@ -176,9 +180,9 @@ grd_rdp_display_control_dispose (GObject *object)
     }
   if (display_control->subscribed_status)
     {
-      grd_session_rdp_unsubscribe_dvc_creation_status (display_control->session_rdp,
-                                                       display_control->channel_id,
-                                                       display_control->dvc_subscription_id);
+      grd_rdp_dvc_unsubscribe_dvc_creation_status (display_control->rdp_dvc,
+                                                   display_control->channel_id,
+                                                   display_control->dvc_subscription_id);
       display_control->subscribed_status = FALSE;
     }
 
diff --git a/src/grd-rdp-display-control.h b/src/grd-rdp-display-control.h
index a8f195fb..775fe30d 100644
--- a/src/grd-rdp-display-control.h
+++ b/src/grd-rdp-display-control.h
@@ -30,6 +30,7 @@ G_DECLARE_FINAL_TYPE (GrdRdpDisplayControl, grd_rdp_display_control,
                       GRD, RDP_DISPLAY_CONTROL, GObject)
 
 GrdRdpDisplayControl *grd_rdp_display_control_new (GrdSessionRdp *session_rdp,
+                                                   GrdRdpDvc     *rdp_dvc,
                                                    HANDLE         vcm,
                                                    HANDLE         stop_event,
                                                    uint32_t       max_monitor_count);
diff --git a/src/grd-rdp-dvc.c b/src/grd-rdp-dvc.c
new file mode 100644
index 00000000..b7d45081
--- /dev/null
+++ b/src/grd-rdp-dvc.c
@@ -0,0 +1,330 @@
+/*
+ * 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-dvc.h"
+
+#include "grd-rdp-private.h"
+
+typedef struct _DVCSubscription
+{
+  gboolean notified;
+
+  GrdRdpDVCCreationStatusCallback callback;
+  gpointer user_data;
+} DVCSubscription;
+
+typedef struct _DVCNotification
+{
+  int32_t creation_status;
+  gboolean pending_status;
+
+  GHashTable *subscriptions;
+  uint32_t next_subscription_id;
+} DVCNotification;
+
+struct _GrdRdpDvc
+{
+  GObject parent;
+
+  GMutex dvc_notification_mutex;
+  GHashTable *dvc_table;
+  GSource *dvc_notification_source;
+};
+
+G_DEFINE_TYPE (GrdRdpDvc, grd_rdp_dvc, G_TYPE_OBJECT)
+
+static DVCNotification *
+dvc_notification_new (void)
+{
+  DVCNotification *dvc_notification;
+
+  dvc_notification = g_new0 (DVCNotification, 1);
+  dvc_notification->pending_status = TRUE;
+  dvc_notification->subscriptions = g_hash_table_new_full (NULL, NULL,
+                                                           NULL, g_free);
+
+  return dvc_notification;
+}
+
+static uint32_t
+get_next_free_dvc_subscription_id (DVCNotification *dvc_notification)
+{
+  uint32_t subscription_id = dvc_notification->next_subscription_id;
+
+  while (g_hash_table_contains (dvc_notification->subscriptions,
+                                GUINT_TO_POINTER (subscription_id)))
+    ++subscription_id;
+
+  dvc_notification->next_subscription_id = subscription_id + 1;
+
+  return subscription_id;
+}
+
+static uint32_t
+dvc_notification_add_subscription (DVCNotification *dvc_notification,
+                                   DVCSubscription *dvc_subscription)
+{
+  uint32_t subscription_id;
+
+  subscription_id = get_next_free_dvc_subscription_id (dvc_notification);
+  g_hash_table_insert (dvc_notification->subscriptions,
+                       GUINT_TO_POINTER (subscription_id), dvc_subscription);
+
+  return subscription_id;
+}
+
+uint32_t
+grd_rdp_dvc_subscribe_dvc_creation_status (GrdRdpDvc                       *rdp_dvc,
+                                           uint32_t                         channel_id,
+                                           GrdRdpDVCCreationStatusCallback  callback,
+                                           gpointer                         callback_user_data)
+{
+  DVCNotification *dvc_notification;
+  g_autofree DVCSubscription *dvc_subscription = NULL;
+  uint32_t subscription_id;
+  gboolean pending_notification = FALSE;
+
+  dvc_subscription = g_new0 (DVCSubscription, 1);
+  dvc_subscription->callback = callback;
+  dvc_subscription->user_data = callback_user_data;
+
+  g_mutex_lock (&rdp_dvc->dvc_notification_mutex);
+  if (g_hash_table_lookup_extended (rdp_dvc->dvc_table,
+                                    GUINT_TO_POINTER (channel_id),
+                                    NULL, (gpointer *) &dvc_notification))
+    {
+      subscription_id =
+        dvc_notification_add_subscription (dvc_notification,
+                                           g_steal_pointer (&dvc_subscription));
+
+      if (!dvc_notification->pending_status)
+        pending_notification = TRUE;
+    }
+  else
+    {
+      dvc_notification = dvc_notification_new ();
+
+      subscription_id =
+        dvc_notification_add_subscription (dvc_notification,
+                                           g_steal_pointer (&dvc_subscription));
+
+      g_hash_table_insert (rdp_dvc->dvc_table,
+                           GUINT_TO_POINTER (channel_id), dvc_notification);
+    }
+  g_mutex_unlock (&rdp_dvc->dvc_notification_mutex);
+
+  if (pending_notification)
+    g_source_set_ready_time (rdp_dvc->dvc_notification_source, 0);
+
+  return subscription_id;
+}
+
+void
+grd_rdp_dvc_unsubscribe_dvc_creation_status (GrdRdpDvc *rdp_dvc,
+                                             uint32_t   channel_id,
+                                             uint32_t   subscription_id)
+{
+  DVCNotification *dvc_notification;
+
+  g_mutex_lock (&rdp_dvc->dvc_notification_mutex);
+  if (!g_hash_table_lookup_extended (rdp_dvc->dvc_table,
+                                     GUINT_TO_POINTER (channel_id),
+                                     NULL, (gpointer *) &dvc_notification))
+    g_assert_not_reached ();
+
+  g_hash_table_remove (dvc_notification->subscriptions,
+                       GUINT_TO_POINTER (subscription_id));
+  g_mutex_unlock (&rdp_dvc->dvc_notification_mutex);
+}
+
+static BOOL
+dvc_creation_status (void     *user_data,
+                     uint32_t  channel_id,
+                     int32_t   creation_status)
+{
+  RdpPeerContext *rdp_peer_context = user_data;
+  GrdRdpDvc *rdp_dvc = rdp_peer_context->rdp_dvc;
+  DVCNotification *dvc_notification;
+  gboolean pending_notification = FALSE;
+
+  g_debug ("[RDP.DRDYNVC] DVC channel id %u creation status: %i",
+           channel_id, creation_status);
+
+  g_mutex_lock (&rdp_dvc->dvc_notification_mutex);
+  if (g_hash_table_lookup_extended (rdp_dvc->dvc_table,
+                                    GUINT_TO_POINTER (channel_id),
+                                    NULL, (gpointer *) &dvc_notification))
+    {
+      if (dvc_notification->pending_status)
+        {
+          dvc_notification->creation_status = creation_status;
+          dvc_notification->pending_status = FALSE;
+
+          if (g_hash_table_size (dvc_notification->subscriptions) > 0)
+            pending_notification = TRUE;
+        }
+      else
+        {
+          g_warning ("[RDP.DRDYNVC] Status of channel %u already known. "
+                     "Discarding result", channel_id);
+        }
+    }
+  else
+    {
+      dvc_notification = dvc_notification_new ();
+
+      dvc_notification->creation_status = creation_status;
+      dvc_notification->pending_status = FALSE;
+
+      g_hash_table_insert (rdp_dvc->dvc_table,
+                           GUINT_TO_POINTER (channel_id), dvc_notification);
+    }
+  g_mutex_unlock (&rdp_dvc->dvc_notification_mutex);
+
+  if (pending_notification)
+    g_source_set_ready_time (rdp_dvc->dvc_notification_source, 0);
+
+  return TRUE;
+}
+
+GrdRdpDvc *
+grd_rdp_dvc_new (HANDLE      vcm,
+                 rdpContext *rdp_context)
+{
+  GrdRdpDvc *rdp_dvc;
+
+  rdp_dvc = g_object_new (GRD_TYPE_RDP_DVC, NULL);
+
+  WTSVirtualChannelManagerSetDVCCreationCallback (vcm, dvc_creation_status,
+                                                  rdp_context);
+
+  return rdp_dvc;
+}
+
+static void
+grd_rdp_dvc_dispose (GObject *object)
+{
+  GrdRdpDvc *rdp_dvc = GRD_RDP_DVC (object);
+
+  if (rdp_dvc->dvc_notification_source)
+    {
+      g_source_destroy (rdp_dvc->dvc_notification_source);
+      g_clear_pointer (&rdp_dvc->dvc_notification_source, g_source_unref);
+    }
+
+  g_clear_pointer (&rdp_dvc->dvc_table, g_hash_table_unref);
+
+  G_OBJECT_CLASS (grd_rdp_dvc_parent_class)->dispose (object);
+}
+
+static void
+grd_rdp_dvc_finalize (GObject *object)
+{
+  GrdRdpDvc *rdp_dvc = GRD_RDP_DVC (object);
+
+  g_mutex_clear (&rdp_dvc->dvc_notification_mutex);
+
+  G_OBJECT_CLASS (grd_rdp_dvc_parent_class)->finalize (object);
+}
+
+static void
+dvc_notification_free (gpointer data)
+{
+  DVCNotification *dvc_notification = data;
+
+  g_clear_pointer (&dvc_notification->subscriptions, g_hash_table_unref);
+
+  g_free (dvc_notification);
+}
+
+static gboolean
+notify_channels (gpointer user_data)
+{
+  GrdRdpDvc *rdp_dvc = user_data;
+  GHashTableIter iter;
+  DVCNotification *dvc_notification;
+
+  g_mutex_lock (&rdp_dvc->dvc_notification_mutex);
+  g_hash_table_iter_init (&iter, rdp_dvc->dvc_table);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &dvc_notification))
+    {
+      GHashTableIter iter2;
+      DVCSubscription *dvc_subscription;
+
+      if (dvc_notification->pending_status)
+        continue;
+
+      g_hash_table_iter_init (&iter2, dvc_notification->subscriptions);
+      while (g_hash_table_iter_next (&iter2, NULL, (gpointer *) &dvc_subscription))
+        {
+          if (dvc_subscription->notified)
+            continue;
+
+          dvc_subscription->callback (dvc_subscription->user_data,
+                                      dvc_notification->creation_status);
+
+          dvc_subscription->notified = TRUE;
+        }
+    }
+  g_mutex_unlock (&rdp_dvc->dvc_notification_mutex);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+source_dispatch (GSource     *source,
+                 GSourceFunc  callback,
+                 gpointer     user_data)
+{
+  g_source_set_ready_time (source, -1);
+
+  return callback (user_data);
+}
+
+static GSourceFuncs source_funcs =
+{
+  .dispatch = source_dispatch,
+};
+
+static void
+grd_rdp_dvc_init (GrdRdpDvc *rdp_dvc)
+{
+  rdp_dvc->dvc_table = g_hash_table_new_full (NULL, NULL,
+                                              NULL, dvc_notification_free);
+
+  g_mutex_init (&rdp_dvc->dvc_notification_mutex);
+
+  rdp_dvc->dvc_notification_source = g_source_new (&source_funcs,
+                                                   sizeof (GSource));
+  g_source_set_callback (rdp_dvc->dvc_notification_source,
+                         notify_channels, rdp_dvc, NULL);
+  g_source_set_ready_time (rdp_dvc->dvc_notification_source, -1);
+  g_source_attach (rdp_dvc->dvc_notification_source, NULL);
+}
+
+static void
+grd_rdp_dvc_class_init (GrdRdpDvcClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = grd_rdp_dvc_dispose;
+  object_class->finalize = grd_rdp_dvc_finalize;
+}
diff --git a/src/grd-rdp-dvc.h b/src/grd-rdp-dvc.h
new file mode 100644
index 00000000..e8139974
--- /dev/null
+++ b/src/grd-rdp-dvc.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef GRD_RDP_DVC_H
+#define GRD_RDP_DVC_H
+
+#include <freerdp/channels/wtsvc.h>
+#include <glib-object.h>
+
+#define GRD_TYPE_RDP_DVC (grd_rdp_dvc_get_type ())
+G_DECLARE_FINAL_TYPE (GrdRdpDvc, grd_rdp_dvc,
+                      GRD, RDP_DVC, GObject)
+
+typedef void (* GrdRdpDVCCreationStatusCallback) (gpointer user_data,
+                                                  int32_t  creation_status);
+
+GrdRdpDvc *grd_rdp_dvc_new (HANDLE      vcm,
+                            rdpContext *rdp_context);
+
+uint32_t grd_rdp_dvc_subscribe_dvc_creation_status (GrdRdpDvc                       *rdp_dvc,
+                                                    uint32_t                         channel_id,
+                                                    GrdRdpDVCCreationStatusCallback  callback,
+                                                    gpointer                         callback_user_data);
+
+void grd_rdp_dvc_unsubscribe_dvc_creation_status (GrdRdpDvc *rdp_dvc,
+                                                  uint32_t   channel_id,
+                                                  uint32_t   subscription_id);
+
+#endif /* GRD_RDP_DVC_H */
diff --git a/src/grd-rdp-graphics-pipeline.c b/src/grd-rdp-graphics-pipeline.c
index bf244432..e16214e0 100644
--- a/src/grd-rdp-graphics-pipeline.c
+++ b/src/grd-rdp-graphics-pipeline.c
@@ -27,6 +27,7 @@
 #include "grd-hwaccel-nvidia.h"
 #include "grd-rdp-buffer.h"
 #include "grd-rdp-damage-detector.h"
+#include "grd-rdp-dvc.h"
 #include "grd-rdp-frame-info.h"
 #include "grd-rdp-gfx-frame-controller.h"
 #include "grd-rdp-gfx-surface.h"
@@ -82,6 +83,7 @@ struct _GrdRdpGraphicsPipeline
   gboolean subscribed_status;
 
   GrdSessionRdp *session_rdp;
+  GrdRdpDvc *rdp_dvc;
   GMainContext *pipeline_context;
   GrdRdpNetworkAutodetection *network_autodetection;
   wStream *encode_stream;
@@ -1128,16 +1130,15 @@ rdpgfx_channel_id_assigned (RdpgfxServerContext *rdpgfx_context,
                             uint32_t             channel_id)
 {
   GrdRdpGraphicsPipeline *graphics_pipeline = rdpgfx_context->custom;
-  GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
 
   g_debug ("[RDP.RDPGFX] DVC channel id assigned to id %u", channel_id);
   graphics_pipeline->channel_id = channel_id;
 
   graphics_pipeline->dvc_subscription_id =
-    grd_session_rdp_subscribe_dvc_creation_status (session_rdp,
-                                                   channel_id,
-                                                   dvc_creation_status,
-                                                   graphics_pipeline);
+    grd_rdp_dvc_subscribe_dvc_creation_status (graphics_pipeline->rdp_dvc,
+                                               channel_id,
+                                               dvc_creation_status,
+                                               graphics_pipeline);
   graphics_pipeline->subscribed_status = TRUE;
 
   return TRUE;
@@ -1488,6 +1489,7 @@ grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline)
 
 GrdRdpGraphicsPipeline *
 grd_rdp_graphics_pipeline_new (GrdSessionRdp              *session_rdp,
+                               GrdRdpDvc                  *rdp_dvc,
                                GMainContext               *pipeline_context,
                                HANDLE                      vcm,
                                HANDLE                      stop_event,
@@ -1507,6 +1509,7 @@ grd_rdp_graphics_pipeline_new (GrdSessionRdp              *session_rdp,
   graphics_pipeline->rdpgfx_context = rdpgfx_context;
   graphics_pipeline->stop_event = stop_event;
   graphics_pipeline->session_rdp = session_rdp;
+  graphics_pipeline->rdp_dvc = rdp_dvc;
   graphics_pipeline->pipeline_context = pipeline_context;
   graphics_pipeline->network_autodetection = network_autodetection;
   graphics_pipeline->encode_stream = encode_stream;
@@ -1579,9 +1582,9 @@ grd_rdp_graphics_pipeline_dispose (GObject *object)
     }
   if (graphics_pipeline->subscribed_status)
     {
-      grd_session_rdp_unsubscribe_dvc_creation_status (graphics_pipeline->session_rdp,
-                                                       graphics_pipeline->channel_id,
-                                                       graphics_pipeline->dvc_subscription_id);
+      grd_rdp_dvc_unsubscribe_dvc_creation_status (graphics_pipeline->rdp_dvc,
+                                                   graphics_pipeline->channel_id,
+                                                   graphics_pipeline->dvc_subscription_id);
       graphics_pipeline->subscribed_status = FALSE;
     }
 
diff --git a/src/grd-rdp-graphics-pipeline.h b/src/grd-rdp-graphics-pipeline.h
index 9bf2fc8b..3adfc207 100644
--- a/src/grd-rdp-graphics-pipeline.h
+++ b/src/grd-rdp-graphics-pipeline.h
@@ -30,6 +30,7 @@ G_DECLARE_FINAL_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline,
                       GRD, RDP_GRAPHICS_PIPELINE, GObject)
 
 GrdRdpGraphicsPipeline *grd_rdp_graphics_pipeline_new (GrdSessionRdp              *session_rdp,
+                                                       GrdRdpDvc                  *rdp_dvc,
                                                        GMainContext               *pipeline_context,
                                                        HANDLE                      vcm,
                                                        HANDLE                      stop_event,
diff --git a/src/grd-rdp-private.h b/src/grd-rdp-private.h
index f70f9bc3..228de8ff 100644
--- a/src/grd-rdp-private.h
+++ b/src/grd-rdp-private.h
@@ -41,6 +41,8 @@ typedef struct _RdpPeerContext
   /* Virtual Channel Manager */
   HANDLE vcm;
 
+  GrdRdpDvc *rdp_dvc;
+
   GMutex channel_mutex;
 
   GrdClipboardRdp *clipboard_rdp;
diff --git a/src/grd-rdp-telemetry.c b/src/grd-rdp-telemetry.c
index 051ffc1b..1540fbe7 100644
--- a/src/grd-rdp-telemetry.c
+++ b/src/grd-rdp-telemetry.c
@@ -21,6 +21,7 @@
 
 #include "grd-rdp-telemetry.h"
 
+#include "grd-rdp-dvc.h"
 #include "grd-session-rdp.h"
 
 #define PROTOCOL_TIMEOUT_MS (10 * 1000)
@@ -39,6 +40,7 @@ struct _GrdRdpTelemetry
   gboolean subscribed_status;
 
   GrdSessionRdp *session_rdp;
+  GrdRdpDvc *rdp_dvc;
 
   GMutex protocol_timeout_mutex;
   GSource *channel_teardown_source;
@@ -119,10 +121,10 @@ telemetry_channel_id_assigned (TelemetryServerContext *telemetry_context,
   telemetry->channel_id = channel_id;
 
   telemetry->dvc_subscription_id =
-    grd_session_rdp_subscribe_dvc_creation_status (telemetry->session_rdp,
-                                                   channel_id,
-                                                   dvc_creation_status,
-                                                   telemetry);
+    grd_rdp_dvc_subscribe_dvc_creation_status (telemetry->rdp_dvc,
+                                               channel_id,
+                                               dvc_creation_status,
+                                               telemetry);
   telemetry->subscribed_status = TRUE;
 
   return TRUE;
@@ -169,6 +171,7 @@ telemetry_rdp_telemetry (TelemetryServerContext            *telemetry_context,
 
 GrdRdpTelemetry *
 grd_rdp_telemetry_new (GrdSessionRdp *session_rdp,
+                       GrdRdpDvc     *rdp_dvc,
                        HANDLE         vcm,
                        HANDLE         stop_event,
                        rdpContext    *rdp_context)
@@ -184,6 +187,7 @@ grd_rdp_telemetry_new (GrdSessionRdp *session_rdp,
   telemetry->telemetry_context = telemetry_context;
   telemetry->stop_event = stop_event;
   telemetry->session_rdp = session_rdp;
+  telemetry->rdp_dvc = rdp_dvc;
 
   telemetry_context->ChannelIdAssigned = telemetry_channel_id_assigned;
   telemetry_context->RdpTelemetry = telemetry_rdp_telemetry;
@@ -205,9 +209,9 @@ grd_rdp_telemetry_dispose (GObject *object)
     }
   if (telemetry->subscribed_status)
     {
-      grd_session_rdp_unsubscribe_dvc_creation_status (telemetry->session_rdp,
-                                                       telemetry->channel_id,
-                                                       telemetry->dvc_subscription_id);
+      grd_rdp_dvc_unsubscribe_dvc_creation_status (telemetry->rdp_dvc,
+                                                   telemetry->channel_id,
+                                                   telemetry->dvc_subscription_id);
       telemetry->subscribed_status = FALSE;
     }
 
diff --git a/src/grd-rdp-telemetry.h b/src/grd-rdp-telemetry.h
index 09c5a833..197577e1 100644
--- a/src/grd-rdp-telemetry.h
+++ b/src/grd-rdp-telemetry.h
@@ -30,6 +30,7 @@ G_DECLARE_FINAL_TYPE (GrdRdpTelemetry, grd_rdp_telemetry,
                       GRD, RDP_TELEMETRY, GObject)
 
 GrdRdpTelemetry *grd_rdp_telemetry_new (GrdSessionRdp *session_rdp,
+                                        GrdRdpDvc     *rdp_dvc,
                                         HANDLE         vcm,
                                         HANDLE         stop_event,
                                         rdpContext    *rdp_context);
diff --git a/src/grd-session-rdp.c b/src/grd-session-rdp.c
index 67d82693..c2ff29ce 100644
--- a/src/grd-session-rdp.c
+++ b/src/grd-session-rdp.c
@@ -36,6 +36,7 @@
 #include "grd-rdp-buffer.h"
 #include "grd-rdp-damage-detector.h"
 #include "grd-rdp-display-control.h"
+#include "grd-rdp-dvc.h"
 #include "grd-rdp-event-queue.h"
 #include "grd-rdp-graphics-pipeline.h"
 #include "grd-rdp-network-autodetection.h"
@@ -89,23 +90,6 @@ typedef struct _SessionMetrics
   uint32_t skipped_frames;
 } SessionMetrics;
 
-typedef struct _DVCSubscription
-{
-  gboolean notified;
-
-  GrdRdpDVCCreationStatusCallback callback;
-  gpointer user_data;
-} DVCSubscription;
-
-typedef struct _DVCNotification
-{
-  int32_t creation_status;
-  gboolean pending_status;
-
-  GHashTable *subscriptions;
-  uint32_t next_subscription_id;
-} DVCNotification;
-
 typedef struct _Pointer
 {
   uint8_t *bitmap;
@@ -168,10 +152,6 @@ struct _GrdSessionRdp
   GThread *graphics_thread;
   GMainContext *graphics_context;
 
-  GMutex dvc_notification_mutex;
-  GHashTable *dvc_table;
-  GSource *dvc_notification_source;
-
   GrdRdpSurface *rdp_surface;
   Pointer *last_pointer;
   GHashTable *pointer_cache;
@@ -698,110 +678,6 @@ grd_session_rdp_notify_error (GrdSessionRdp      *session_rdp,
   maybe_queue_close_session_idle (session_rdp);
 }
 
-static DVCNotification *
-dvc_notification_new (void)
-{
-  DVCNotification *dvc_notification;
-
-  dvc_notification = g_new0 (DVCNotification, 1);
-  dvc_notification->pending_status = TRUE;
-  dvc_notification->subscriptions = g_hash_table_new_full (NULL, NULL,
-                                                           NULL, g_free);
-
-  return dvc_notification;
-}
-
-static uint32_t
-get_next_free_dvc_subscription_id (DVCNotification *dvc_notification)
-{
-  uint32_t subscription_id = dvc_notification->next_subscription_id;
-
-  while (g_hash_table_contains (dvc_notification->subscriptions,
-                                GUINT_TO_POINTER (subscription_id)))
-    ++subscription_id;
-
-  dvc_notification->next_subscription_id = subscription_id + 1;
-
-  return subscription_id;
-}
-
-static uint32_t
-dvc_notification_add_subscription (DVCNotification *dvc_notification,
-                                   DVCSubscription *dvc_subscription)
-{
-  uint32_t subscription_id;
-
-  subscription_id = get_next_free_dvc_subscription_id (dvc_notification);
-  g_hash_table_insert (dvc_notification->subscriptions,
-                       GUINT_TO_POINTER (subscription_id), dvc_subscription);
-
-  return subscription_id;
-}
-
-uint32_t
-grd_session_rdp_subscribe_dvc_creation_status (GrdSessionRdp                   *session_rdp,
-                                               uint32_t                         channel_id,
-                                               GrdRdpDVCCreationStatusCallback  callback,
-                                               gpointer                         callback_user_data)
-{
-  DVCNotification *dvc_notification;
-  g_autofree DVCSubscription *dvc_subscription = NULL;
-  uint32_t subscription_id;
-  gboolean pending_notification = FALSE;
-
-  dvc_subscription = g_new0 (DVCSubscription, 1);
-  dvc_subscription->callback = callback;
-  dvc_subscription->user_data = callback_user_data;
-
-  g_mutex_lock (&session_rdp->dvc_notification_mutex);
-  if (g_hash_table_lookup_extended (session_rdp->dvc_table,
-                                    GUINT_TO_POINTER (channel_id),
-                                    NULL, (gpointer *) &dvc_notification))
-    {
-      subscription_id =
-        dvc_notification_add_subscription (dvc_notification,
-                                           g_steal_pointer (&dvc_subscription));
-
-      if (!dvc_notification->pending_status)
-        pending_notification = TRUE;
-    }
-  else
-    {
-      dvc_notification = dvc_notification_new ();
-
-      subscription_id =
-        dvc_notification_add_subscription (dvc_notification,
-                                           g_steal_pointer (&dvc_subscription));
-
-      g_hash_table_insert (session_rdp->dvc_table,
-                           GUINT_TO_POINTER (channel_id), dvc_notification);
-    }
-  g_mutex_unlock (&session_rdp->dvc_notification_mutex);
-
-  if (pending_notification)
-    g_source_set_ready_time (session_rdp->dvc_notification_source, 0);
-
-  return subscription_id;
-}
-
-void
-grd_session_rdp_unsubscribe_dvc_creation_status (GrdSessionRdp *session_rdp,
-                                                 uint32_t       channel_id,
-                                                 uint32_t       subscription_id)
-{
-  DVCNotification *dvc_notification;
-
-  g_mutex_lock (&session_rdp->dvc_notification_mutex);
-  if (!g_hash_table_lookup_extended (session_rdp->dvc_table,
-                                     GUINT_TO_POINTER (channel_id),
-                                     NULL, (gpointer *) &dvc_notification))
-    g_assert_not_reached ();
-
-  g_hash_table_remove (dvc_notification->subscriptions,
-                       GUINT_TO_POINTER (subscription_id));
-  g_mutex_unlock (&session_rdp->dvc_notification_mutex);
-}
-
 void
 grd_session_rdp_tear_down_channel (GrdSessionRdp *session_rdp,
                                    GrdRdpChannel  channel)
@@ -2034,6 +1910,8 @@ rdp_peer_context_free (freerdp_peer   *peer,
   if (!rdp_peer_context)
     return;
 
+  g_clear_object (&rdp_peer_context->rdp_dvc);
+
   g_clear_pointer (&rdp_peer_context->vcm, WTSCloseServer);
 
   if (rdp_peer_context->encode_stream)
@@ -2047,56 +1925,6 @@ rdp_peer_context_free (freerdp_peer   *peer,
   g_mutex_clear (&rdp_peer_context->channel_mutex);
 }
 
-static BOOL
-dvc_creation_status (void     *user_data,
-                     uint32_t  channel_id,
-                     int32_t   creation_status)
-{
-  RdpPeerContext *rdp_peer_context = user_data;
-  GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp;
-  DVCNotification *dvc_notification;
-  gboolean pending_notification = FALSE;
-
-  g_debug ("[RDP.DRDYNVC] DVC channel id %u creation status: %i",
-           channel_id, creation_status);
-
-  g_mutex_lock (&session_rdp->dvc_notification_mutex);
-  if (g_hash_table_lookup_extended (session_rdp->dvc_table,
-                                    GUINT_TO_POINTER (channel_id),
-                                    NULL, (gpointer *) &dvc_notification))
-    {
-      if (dvc_notification->pending_status)
-        {
-          dvc_notification->creation_status = creation_status;
-          dvc_notification->pending_status = FALSE;
-
-          if (g_hash_table_size (dvc_notification->subscriptions) > 0)
-            pending_notification = TRUE;
-        }
-      else
-        {
-          g_warning ("[RDP.DRDYNVC] Status of channel %u already known. "
-                     "Discarding result", channel_id);
-        }
-    }
-  else
-    {
-      dvc_notification = dvc_notification_new ();
-
-      dvc_notification->creation_status = creation_status;
-      dvc_notification->pending_status = FALSE;
-
-      g_hash_table_insert (session_rdp->dvc_table,
-                           GUINT_TO_POINTER (channel_id), dvc_notification);
-    }
-  g_mutex_unlock (&session_rdp->dvc_notification_mutex);
-
-  if (pending_notification)
-    g_source_set_ready_time (session_rdp->dvc_notification_source, 0);
-
-  return TRUE;
-}
-
 static BOOL
 rdp_peer_context_new (freerdp_peer   *peer,
                       RdpPeerContext *rdp_peer_context)
@@ -2138,9 +1966,8 @@ rdp_peer_context_new (freerdp_peer   *peer,
       return FALSE;
     }
 
-  WTSVirtualChannelManagerSetDVCCreationCallback (rdp_peer_context->vcm,
-                                                  dvc_creation_status,
-                                                  rdp_peer_context);
+  rdp_peer_context->rdp_dvc = grd_rdp_dvc_new (rdp_peer_context->vcm,
+                                               &rdp_peer_context->rdp_context);
 
   return TRUE;
 }
@@ -2477,11 +2304,6 @@ clear_session_sources (GrdSessionRdp *session_rdp)
       g_source_destroy (session_rdp->pending_encode_source);
       g_clear_pointer (&session_rdp->pending_encode_source, g_source_unref);
     }
-  if (session_rdp->dvc_notification_source)
-    {
-      g_source_destroy (session_rdp->dvc_notification_source);
-      g_clear_pointer (&session_rdp->dvc_notification_source, g_source_unref);
-    }
 }
 
 static gboolean
@@ -2609,6 +2431,7 @@ maybe_initialize_graphics_pipeline (GrdSessionRdp *session_rdp)
   g_assert (is_rdp_peer_flag_set (session_rdp, RDP_PEER_PENDING_GFX_INIT));
 
   telemetry = grd_rdp_telemetry_new (session_rdp,
+                                     rdp_peer_context->rdp_dvc,
                                      rdp_peer_context->vcm,
                                      session_rdp->stop_event,
                                      peer->context);
@@ -2616,6 +2439,7 @@ maybe_initialize_graphics_pipeline (GrdSessionRdp *session_rdp)
 
   graphics_pipeline =
     grd_rdp_graphics_pipeline_new (session_rdp,
+                                   rdp_peer_context->rdp_dvc,
                                    session_rdp->graphics_context,
                                    rdp_peer_context->vcm,
                                    session_rdp->stop_event,
@@ -2634,21 +2458,22 @@ initialize_remaining_virtual_channels (GrdSessionRdp *session_rdp)
   freerdp_peer *peer = session_rdp->peer;
   RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context;
   rdpSettings *rdp_settings = peer->settings;
+  GrdRdpDvc *rdp_dvc = rdp_peer_context->rdp_dvc;
+  HANDLE vcm = rdp_peer_context->vcm;
 
-  if (WTSVirtualChannelManagerIsChannelJoined (rdp_peer_context->vcm,
-                                               "cliprdr"))
+  if (WTSVirtualChannelManagerIsChannelJoined (vcm, "cliprdr"))
     {
       gboolean peer_is_on_ms_windows;
 
       peer_is_on_ms_windows = rdp_settings->OsMajorType == OSMAJORTYPE_WINDOWS;
       rdp_peer_context->clipboard_rdp =
-        grd_clipboard_rdp_new (session_rdp, rdp_peer_context->vcm,
-                               session_rdp->stop_event, !peer_is_on_ms_windows);
+        grd_clipboard_rdp_new (session_rdp, vcm, session_rdp->stop_event,
+                               !peer_is_on_ms_windows);
     }
   if (rdp_settings->AudioPlayback && !rdp_settings->RemoteConsoleAudio)
     {
       rdp_peer_context->audio_playback =
-        grd_rdp_audio_playback_new (session_rdp, rdp_peer_context->vcm,
+        grd_rdp_audio_playback_new (session_rdp, rdp_dvc, vcm,
                                     session_rdp->stop_event, peer->context);
     }
 }
@@ -2746,6 +2571,7 @@ grd_session_rdp_stream_ready (GrdSession *session,
         {
           rdp_peer_context->display_control =
             grd_rdp_display_control_new (session_rdp,
+                                         rdp_peer_context->rdp_dvc,
                                          rdp_peer_context->vcm,
                                          session_rdp->stop_event,
                                          MAX_MONITOR_COUNT);
@@ -2788,7 +2614,6 @@ grd_session_rdp_dispose (GObject *object)
   g_clear_pointer (&session_rdp->pressed_unicode_keys, g_hash_table_unref);
   g_clear_pointer (&session_rdp->pressed_keys, g_hash_table_unref);
   g_clear_pointer (&session_rdp->pointer_cache, g_hash_table_unref);
-  g_clear_pointer (&session_rdp->dvc_table, g_hash_table_unref);
 
   g_clear_pointer (&session_rdp->stop_event, CloseHandle);
 
@@ -2802,7 +2627,6 @@ grd_session_rdp_finalize (GObject *object)
 
   g_mutex_clear (&session_rdp->monitor_config_mutex);
   g_mutex_clear (&session_rdp->close_session_mutex);
-  g_mutex_clear (&session_rdp->dvc_notification_mutex);
   g_mutex_clear (&session_rdp->rdp_flags_mutex);
   g_mutex_clear (&session_rdp->pending_jobs_mutex);
   g_cond_clear (&session_rdp->pending_jobs_cond);
@@ -2810,16 +2634,6 @@ grd_session_rdp_finalize (GObject *object)
   G_OBJECT_CLASS (grd_session_rdp_parent_class)->finalize (object);
 }
 
-static void
-dvc_notification_free (gpointer data)
-{
-  DVCNotification *dvc_notification = data;
-
-  g_clear_pointer (&dvc_notification->subscriptions, g_hash_table_unref);
-
-  g_free (dvc_notification);
-}
-
 static gboolean
 are_pointer_bitmaps_equal (gconstpointer a,
                            gconstpointer b)
@@ -2846,40 +2660,6 @@ are_pointer_bitmaps_equal (gconstpointer a,
   return TRUE;
 }
 
-static gboolean
-notify_channels (gpointer user_data)
-{
-  GrdSessionRdp *session_rdp = user_data;
-  GHashTableIter iter;
-  DVCNotification *dvc_notification;
-
-  g_mutex_lock (&session_rdp->dvc_notification_mutex);
-  g_hash_table_iter_init (&iter, session_rdp->dvc_table);
-  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &dvc_notification))
-    {
-      GHashTableIter iter2;
-      DVCSubscription *dvc_subscription;
-
-      if (dvc_notification->pending_status)
-        continue;
-
-      g_hash_table_iter_init (&iter2, dvc_notification->subscriptions);
-      while (g_hash_table_iter_next (&iter2, NULL, (gpointer *) &dvc_subscription))
-        {
-          if (dvc_subscription->notified)
-            continue;
-
-          dvc_subscription->callback (dvc_subscription->user_data,
-                                      dvc_notification->creation_status);
-
-          dvc_subscription->notified = TRUE;
-        }
-    }
-  g_mutex_unlock (&session_rdp->dvc_notification_mutex);
-
-  return G_SOURCE_CONTINUE;
-}
-
 static gboolean
 encode_pending_frames (gpointer user_data)
 {
@@ -2933,8 +2713,6 @@ grd_session_rdp_init (GrdSessionRdp *session_rdp)
 {
   session_rdp->stop_event = CreateEvent (NULL, TRUE, FALSE, NULL);
 
-  session_rdp->dvc_table = g_hash_table_new_full (NULL, NULL,
-                                                  NULL, dvc_notification_free);
   session_rdp->pointer_cache = g_hash_table_new (NULL, are_pointer_bitmaps_equal);
   session_rdp->pressed_keys = g_hash_table_new (NULL, NULL);
   session_rdp->pressed_unicode_keys = g_hash_table_new (NULL, NULL);
@@ -2942,7 +2720,6 @@ grd_session_rdp_init (GrdSessionRdp *session_rdp)
   g_cond_init (&session_rdp->pending_jobs_cond);
   g_mutex_init (&session_rdp->pending_jobs_mutex);
   g_mutex_init (&session_rdp->rdp_flags_mutex);
-  g_mutex_init (&session_rdp->dvc_notification_mutex);
   g_mutex_init (&session_rdp->close_session_mutex);
   g_mutex_init (&session_rdp->monitor_config_mutex);
 
@@ -2950,13 +2727,6 @@ grd_session_rdp_init (GrdSessionRdp *session_rdp)
 
   session_rdp->graphics_context = g_main_context_new ();
 
-  session_rdp->dvc_notification_source = g_source_new (&session_source_funcs,
-                                                       sizeof (GSource));
-  g_source_set_callback (session_rdp->dvc_notification_source,
-                         notify_channels, session_rdp, NULL);
-  g_source_set_ready_time (session_rdp->dvc_notification_source, -1);
-  g_source_attach (session_rdp->dvc_notification_source, NULL);
-
   session_rdp->pending_encode_source = g_source_new (&session_source_funcs,
                                                      sizeof (GSource));
   g_source_set_callback (session_rdp->pending_encode_source,
diff --git a/src/grd-session-rdp.h b/src/grd-session-rdp.h
index 71577a8b..6742ccf7 100644
--- a/src/grd-session-rdp.h
+++ b/src/grd-session-rdp.h
@@ -48,9 +48,6 @@ typedef enum _GrdRdpChannel
   GRD_RDP_CHANNEL_TELEMETRY,
 } GrdRdpChannel;
 
-typedef void (* GrdRdpDVCCreationStatusCallback) (gpointer user_data,
-                                                  int32_t  creation_status);
-
 GrdSessionRdp *grd_session_rdp_new (GrdRdpServer      *rdp_server,
                                     GSocketConnection *connection,
                                     GrdHwAccelNvidia  *hwaccel_nvidia);
@@ -58,15 +55,6 @@ GrdSessionRdp *grd_session_rdp_new (GrdRdpServer      *rdp_server,
 void grd_session_rdp_notify_error (GrdSessionRdp      *session_rdp,
                                    GrdSessionRdpError  error_info);
 
-uint32_t grd_session_rdp_subscribe_dvc_creation_status (GrdSessionRdp                   *session_rdp,
-                                                        uint32_t                         channel_id,
-                                                        GrdRdpDVCCreationStatusCallback  callback,
-                                                        gpointer                         callback_user_data);
-
-void grd_session_rdp_unsubscribe_dvc_creation_status (GrdSessionRdp *session_rdp,
-                                                      uint32_t       channel_id,
-                                                      uint32_t       subscription_id);
-
 void grd_session_rdp_tear_down_channel (GrdSessionRdp *session_rdp,
                                         GrdRdpChannel  channel);
 
diff --git a/src/grd-types.h b/src/grd-types.h
index e2d337fe..123a00c9 100644
--- a/src/grd-types.h
+++ b/src/grd-types.h
@@ -37,6 +37,7 @@ typedef struct _GrdRdpBuffer GrdRdpBuffer;
 typedef struct _GrdRdpBufferPool GrdRdpBufferPool;
 typedef struct _GrdRdpDamageDetector GrdRdpDamageDetector;
 typedef struct _GrdRdpDisplayControl GrdRdpDisplayControl;
+typedef struct _GrdRdpDvc GrdRdpDvc;
 typedef struct _GrdRdpEventQueue GrdRdpEventQueue;
 typedef struct _GrdRdpGfxFrameController GrdRdpGfxFrameController;
 typedef struct _GrdRdpGfxFrameLog GrdRdpGfxFrameLog;
diff --git a/src/meson.build b/src/meson.build
index b274fa43..e1398589 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -87,6 +87,8 @@ if have_rdp
     'grd-rdp-display-control.h',
     'grd-rdp-dsp.c',
     'grd-rdp-dsp.h',
+    'grd-rdp-dvc.c',
+    'grd-rdp-dvc.h',
     'grd-rdp-event-queue.c',
     'grd-rdp-event-queue.h',
     'grd-rdp-frame-info.h',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]