gstreamer/subprojects/gst-plugins-bad/gst-libs/gst/winrt/gstwinrtdevicewatcher.cpp
Seungha Yang 08cb5b482d libs: Introduce GstWinRT library
Adding a helper library for various WinRT specific implementations.
Currently this library supports only DeviceWatcher abstraction object
which can be used for dynamic device add/remove detection.
See also
https://docs.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicewatcher?view=winrt-20348

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/947>
2021-09-30 06:13:07 +00:00

724 lines
19 KiB
C++

/* GStreamer
* Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstwinrtdevicewatcher.h"
/* workaround for GetCurrentTime collision */
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
#include <windows.foundation.h>
#include <wrl.h>
#include <wrl/wrappers/corewrappers.h>
/* *INDENT-OFF* */
typedef __FITypedEventHandler_2_Windows__CDevices__CEnumeration__CDeviceWatcher_Windows__CDevices__CEnumeration__CDeviceInformation IAddedHandler;
typedef __FITypedEventHandler_2_Windows__CDevices__CEnumeration__CDeviceWatcher_Windows__CDevices__CEnumeration__CDeviceInformationUpdate IUpdatedHandler;
typedef __FITypedEventHandler_2_Windows__CDevices__CEnumeration__CDeviceWatcher_Windows__CDevices__CEnumeration__CDeviceInformationUpdate IRemovedHandler;
typedef __FITypedEventHandler_2_Windows__CDevices__CEnumeration__CDeviceWatcher_IInspectable IEnumerationCompletedHandler;
typedef __FITypedEventHandler_2_Windows__CDevices__CEnumeration__CDeviceWatcher_IInspectable IStoppedHandler;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Devices;
using namespace ABI::Windows::Devices::Enumeration;
GST_DEBUG_CATEGORY_STATIC (gst_winrt_device_watcher_debug);
#define GST_CAT_DEFAULT gst_winrt_device_watcher_debug
static void
gst_winrt_device_watcher_device_added (GstWinRTDeviceWatcher * self,
IDeviceInformation * info);
static void
gst_winrt_device_watcher_device_updated (GstWinRTDeviceWatcher * self,
IDeviceInformationUpdate * info_update);
static void
gst_winrt_device_watcher_device_removed (GstWinRTDeviceWatcher * self,
IDeviceInformationUpdate * info_update);
static void
gst_winrt_device_watcher_device_enumeration_completed (GstWinRTDeviceWatcher *
self);
static void
gst_winrt_device_watcher_device_enumeration_stopped (GstWinRTDeviceWatcher *
self);
class AddedHandler
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IAddedHandler>
{
public:
AddedHandler () {}
HRESULT RuntimeClassInitialize (GstWinRTDeviceWatcher * listenr)
{
if (!listenr)
return E_INVALIDARG;
listener_ = listenr;
return S_OK;
}
IFACEMETHOD(Invoke)
(IDeviceWatcher* sender, IDeviceInformation * arg)
{
gst_winrt_device_watcher_device_added (listener_, arg);
return S_OK;
}
private:
GstWinRTDeviceWatcher * listener_;
};
class UpdatedHandler
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IUpdatedHandler>
{
public:
UpdatedHandler () {}
HRESULT RuntimeClassInitialize (GstWinRTDeviceWatcher * listenr)
{
if (!listenr)
return E_INVALIDARG;
listener_ = listenr;
return S_OK;
}
IFACEMETHOD(Invoke)
(IDeviceWatcher* sender, IDeviceInformationUpdate * arg)
{
gst_winrt_device_watcher_device_updated (listener_, arg);
return S_OK;
}
private:
GstWinRTDeviceWatcher * listener_;
};
class RemovedHandler
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IRemovedHandler>
{
public:
RemovedHandler () {}
HRESULT RuntimeClassInitialize (GstWinRTDeviceWatcher * listenr)
{
if (!listenr)
return E_INVALIDARG;
listener_ = listenr;
return S_OK;
}
IFACEMETHOD(Invoke)
(IDeviceWatcher* sender, IDeviceInformationUpdate * arg)
{
gst_winrt_device_watcher_device_removed (listener_, arg);
return S_OK;
}
private:
GstWinRTDeviceWatcher * listener_;
};
class EnumerationCompletedHandler
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IEnumerationCompletedHandler>
{
public:
EnumerationCompletedHandler () {}
HRESULT RuntimeClassInitialize (GstWinRTDeviceWatcher * listenr)
{
if (!listenr)
return E_INVALIDARG;
listener_ = listenr;
return S_OK;
}
IFACEMETHOD(Invoke)
(IDeviceWatcher* sender, IInspectable * arg)
{
gst_winrt_device_watcher_device_enumeration_completed (listener_);
return S_OK;
}
private:
GstWinRTDeviceWatcher * listener_;
};
class StoppedHandler
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IStoppedHandler>
{
public:
StoppedHandler () {}
HRESULT RuntimeClassInitialize (GstWinRTDeviceWatcher * listenr)
{
if (!listenr)
return E_INVALIDARG;
listener_ = listenr;
return S_OK;
}
IFACEMETHOD(Invoke)
(IDeviceWatcher* sender, IInspectable * arg)
{
gst_winrt_device_watcher_device_enumeration_stopped (listener_);
return S_OK;
}
private:
GstWinRTDeviceWatcher * listener_;
};
/* *INDENT-ON* */
typedef struct
{
ComPtr < IDeviceWatcher > watcher;
EventRegistrationToken added_token;
EventRegistrationToken updated_token;
EventRegistrationToken removed_token;
EventRegistrationToken enum_completed_token;
EventRegistrationToken stopped_token;
} GstWinRTDeviceWatcherInner;
enum
{
PROP_0,
PROP_DEVICE_CLASS,
};
#define DEFAULT_DEVICE_CLASS GST_WINRT_DEVICE_CLASS_ALL
struct _GstWinRTDeviceWatcherPrivate
{
GMutex lock;
GCond cond;
GThread *thread;
GMainContext *context;
GMainLoop *loop;
gboolean running;
GstWinRTDeviceWatcherCallbacks callbacks;
gpointer user_data;
GstWinRTDeviceWatcherInner *inner;
GstWinRTDeviceClass device_class;
};
GType
gst_winrt_device_class_get_type (void)
{
static gsize device_class_type = 0;
if (g_once_init_enter (&device_class_type)) {
static const GEnumValue classes[] = {
{GST_WINRT_DEVICE_CLASS_ALL, "All", "all"},
{GST_WINRT_DEVICE_CLASS_AUDIO_CAPTURE, "AudioCapture", "audio-capture"},
{GST_WINRT_DEVICE_CLASS_AUDIO_RENDER, "AudioRender", "audio-render"},
{GST_WINRT_DEVICE_CLASS_PORTABLE_STORAGE_DEVICE,
"PortableStorageDevice", "portable-storage-device"},
{GST_WINRT_DEVICE_CLASS_VIDEO_CAPTURE,
"VideoCapture", "video-capture"},
{0, nullptr, nullptr},
};
GType tmp = g_enum_register_static ("GstWinRTDeviceClass", classes);
g_once_init_leave (&device_class_type, tmp);
}
return (GType) device_class_type;
}
static void gst_winrt_device_watcher_constructed (GObject * object);
static void gst_winrt_device_watcher_finalize (GObject * object);
static void gst_winrt_device_watcher_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_winrt_device_watcher_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static gpointer
gst_winrt_device_watcher_thread_func (GstWinRTDeviceWatcher * self);
#define gst_winrt_device_watcher_parent_class parent_class
G_DEFINE_TYPE_WITH_PRIVATE (GstWinRTDeviceWatcher, gst_winrt_device_watcher,
GST_TYPE_OBJECT);
static void
gst_winrt_device_watcher_class_init (GstWinRTDeviceWatcherClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->constructed = gst_winrt_device_watcher_constructed;
gobject_class->finalize = gst_winrt_device_watcher_finalize;
gobject_class->set_property = gst_winrt_device_watcher_set_property;
gobject_class->get_property = gst_winrt_device_watcher_get_property;
g_object_class_install_property (gobject_class, PROP_DEVICE_CLASS,
g_param_spec_enum ("device-class", "Device Class",
"Device class to watch", GST_TYPE_WINRT_DEVICE_CLASS,
DEFAULT_DEVICE_CLASS,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
GST_DEBUG_CATEGORY_INIT (gst_winrt_device_watcher_debug,
"winrtdevicewatcher", 0, "winrtdevicewatcher");
}
static void
gst_winrt_device_watcher_init (GstWinRTDeviceWatcher * self)
{
GstWinRTDeviceWatcherPrivate *priv;
self->priv = priv = (GstWinRTDeviceWatcherPrivate *)
gst_winrt_device_watcher_get_instance_private (self);
g_mutex_init (&priv->lock);
g_cond_init (&priv->cond);
priv->context = g_main_context_new ();
priv->loop = g_main_loop_new (priv->context, FALSE);
}
static void
gst_winrt_device_watcher_constructed (GObject * object)
{
GstWinRTDeviceWatcher *self = GST_WINRT_DEVICE_WATCHER (object);
GstWinRTDeviceWatcherPrivate *priv = self->priv;
g_mutex_lock (&priv->lock);
priv->thread = g_thread_new ("GstWinRTDeviceWatcher",
(GThreadFunc) gst_winrt_device_watcher_thread_func, self);
while (!g_main_loop_is_running (priv->loop))
g_cond_wait (&priv->cond, &priv->lock);
g_mutex_unlock (&priv->lock);
}
static void
gst_winrt_device_watcher_finalize (GObject * object)
{
GstWinRTDeviceWatcher *self = GST_WINRT_DEVICE_WATCHER (object);
GstWinRTDeviceWatcherPrivate *priv = self->priv;
g_main_loop_quit (priv->loop);
if (g_thread_self () != priv->thread) {
g_thread_join (priv->thread);
g_main_loop_unref (priv->loop);
g_main_context_unref (priv->context);
} else {
g_warning ("Trying join from self-thread");
}
g_mutex_clear (&priv->lock);
g_cond_clear (&priv->cond);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_winrt_device_watcher_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstWinRTDeviceWatcher *self = GST_WINRT_DEVICE_WATCHER (object);
GstWinRTDeviceWatcherPrivate *priv = self->priv;
switch (prop_id) {
case PROP_DEVICE_CLASS:
priv->device_class = (GstWinRTDeviceClass) g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_winrt_device_watcher_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstWinRTDeviceWatcher *self = GST_WINRT_DEVICE_WATCHER (object);
GstWinRTDeviceWatcherPrivate *priv = self->priv;
switch (prop_id) {
case PROP_DEVICE_CLASS:
g_value_set_enum (value, priv->device_class);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
loop_running_cb (GstWinRTDeviceWatcher * self)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
g_mutex_lock (&priv->lock);
g_cond_signal (&priv->cond);
g_mutex_unlock (&priv->lock);
return G_SOURCE_REMOVE;
}
static void
gst_winrt_device_watcher_thread_func_inner (GstWinRTDeviceWatcher * self)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
GSource *idle_source;
HRESULT hr;
GstWinRTDeviceWatcherInner *inner = nullptr;
ComPtr < IDeviceInformationStatics > factory;
ComPtr < IDeviceWatcher > watcher;
ComPtr < IAddedHandler > added_handler;
ComPtr < IUpdatedHandler > updated_handler;
ComPtr < IRemovedHandler > removed_handler;
ComPtr < IEnumerationCompletedHandler > enum_completed_handler;
ComPtr < IStoppedHandler > stopped_handler;
g_main_context_push_thread_default (priv->context);
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source,
(GSourceFunc) loop_running_cb, self, nullptr);
g_source_attach (idle_source, priv->context);
g_source_unref (idle_source);
hr = GetActivationFactory (HStringReference
(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get (),
&factory);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self,
"Failed to get IDeviceInformationStatics, hr: 0x%x", (guint) hr);
goto run_loop;
}
hr = factory->CreateWatcherDeviceClass ((DeviceClass) priv->device_class,
&watcher);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self,
"Failed to create IDeviceWatcher, hr: 0x%x", (guint) hr);
goto run_loop;
}
hr = MakeAndInitialize < AddedHandler > (&added_handler, self);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to create added handler, hr: 0x%x", hr);
goto run_loop;
}
hr = MakeAndInitialize < UpdatedHandler > (&updated_handler, self);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to create updated handler, hr: 0x%x", hr);
goto run_loop;
}
hr = MakeAndInitialize < RemovedHandler > (&removed_handler, self);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to create removed handler, hr: 0x%x", hr);
goto run_loop;
}
hr = MakeAndInitialize < EnumerationCompletedHandler >
(&enum_completed_handler, self);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self,
"Failed to create enumeration completed handler, hr: 0x%x", hr);
goto run_loop;
}
hr = MakeAndInitialize < StoppedHandler > (&stopped_handler, self);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to create stopped handler, hr: 0x%x", hr);
goto run_loop;
}
inner = new GstWinRTDeviceWatcherInner ();
hr = watcher->add_Added (added_handler.Get (), &inner->added_token);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to register added handler, hr: 0x%x", hr);
delete inner;
inner = nullptr;
goto run_loop;
}
hr = watcher->add_Updated (updated_handler.Get (), &inner->updated_token);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to register updated handler, hr: 0x%x", hr);
delete inner;
inner = nullptr;
goto run_loop;
}
hr = watcher->add_Removed (removed_handler.Get (), &inner->removed_token);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to register removed handler, hr: 0x%x", hr);
delete inner;
inner = nullptr;
goto run_loop;
}
hr = watcher->add_EnumerationCompleted (enum_completed_handler.Get (),
&inner->enum_completed_token);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self,
"Failed to register enumeration completed handler, hr: 0x%x", hr);
delete inner;
inner = nullptr;
goto run_loop;
}
hr = watcher->add_Stopped (stopped_handler.Get (), &inner->stopped_token);
if (FAILED (hr)) {
GST_ERROR_OBJECT (self, "Failed to register stopped handler, hr: 0x%x", hr);
delete inner;
inner = nullptr;
goto run_loop;
}
inner->watcher = watcher;
priv->inner = inner;
run_loop:
GST_INFO_OBJECT (self, "Starting loop");
g_main_loop_run (priv->loop);
GST_INFO_OBJECT (self, "Stopped loop");
if (inner) {
if (priv->running)
watcher->Stop ();
watcher->remove_Added (inner->added_token);
watcher->remove_Updated (inner->updated_token);
watcher->remove_Removed (inner->removed_token);
watcher->remove_EnumerationCompleted (inner->enum_completed_token);
watcher->remove_Stopped (inner->stopped_token);
delete inner;
}
g_main_context_pop_thread_default (priv->context);
}
static gpointer
gst_winrt_device_watcher_thread_func (GstWinRTDeviceWatcher * self)
{
RoInitializeWrapper initialize (RO_INIT_MULTITHREADED);
/* wrap with another function so that everything can happen
* before RoInitializeWrapper is destructed */
gst_winrt_device_watcher_thread_func_inner (self);
return nullptr;
}
static void
gst_winrt_device_watcher_device_added (GstWinRTDeviceWatcher * self,
IDeviceInformation * info)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Device added");
if (priv->callbacks.added)
priv->callbacks.added (self, info, priv->user_data);
}
static void
gst_winrt_device_watcher_device_updated (GstWinRTDeviceWatcher * self,
IDeviceInformationUpdate * info_update)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Device updated");
if (priv->callbacks.updated)
priv->callbacks.updated (self, info_update, priv->user_data);
}
static void
gst_winrt_device_watcher_device_removed (GstWinRTDeviceWatcher * self,
IDeviceInformationUpdate * info_update)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Device removed");
if (priv->callbacks.removed)
priv->callbacks.removed (self, info_update, priv->user_data);
}
static void
gst_winrt_device_watcher_device_enumeration_completed (GstWinRTDeviceWatcher *
self)
{
GstWinRTDeviceWatcherPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Enumeration completed");
if (priv->callbacks.enumeration_completed)
priv->callbacks.enumeration_completed (self, priv->user_data);
}
static void
gst_winrt_device_watcher_device_enumeration_stopped (GstWinRTDeviceWatcher *
self)
{
GST_DEBUG_OBJECT (self, "Stopped");
}
/**
* gst_winrt_device_watcher_new:
* @device_class: a #GstWinRTDeviceClass to watch
* @callbacks: a pointer to #GstWinRTDeviceWatcherCallbacks
* @user_data: a user_data argument for the callbacks
*
* Constructs a new #GstWinRTDeviceWatcher object for watching device update
* of @device_class
*
* Returns: (transfer full) (nullable): a new #GstWinRTDeviceWatcher
* or %NULL when failed to create/setup #GstWinRTDeviceWatcher object
*
* Since: 1.20
*/
GstWinRTDeviceWatcher *
gst_winrt_device_watcher_new (GstWinRTDeviceClass device_class,
const GstWinRTDeviceWatcherCallbacks * callbacks, gpointer user_data)
{
GstWinRTDeviceWatcher *self;
GstWinRTDeviceWatcherPrivate *priv;
g_return_val_if_fail (callbacks != nullptr, nullptr);
self = (GstWinRTDeviceWatcher *)
g_object_new (GST_TYPE_WINRT_DEVICE_WATCHER, "device-class", device_class,
nullptr);
priv = self->priv;
if (!priv->inner) {
gst_object_unref (self);
return nullptr;
}
priv->callbacks = *callbacks;
priv->user_data = user_data;
gst_object_ref_sink (self);
return self;
}
/**
* gst_winrt_device_watcher_start:
* @device_class: a #GstWinRTDeviceClass to watch
*
* Starts watching device update.
*
* Returns: %TRUE if successful
*
* Since: 1.20
*/
gboolean
gst_winrt_device_watcher_start (GstWinRTDeviceWatcher * watcher)
{
GstWinRTDeviceWatcherPrivate *priv;
GstWinRTDeviceWatcherInner *inner;
HRESULT hr;
g_return_val_if_fail (GST_IS_WINRT_DEVICE_WATCHER (watcher), FALSE);
priv = watcher->priv;
inner = priv->inner;
GST_DEBUG_OBJECT (watcher, "Start");
g_mutex_lock (&priv->lock);
if (priv->running) {
GST_DEBUG_OBJECT (watcher, "Already running");
g_mutex_unlock (&priv->lock);
return TRUE;
}
hr = inner->watcher->Start ();
if (FAILED (hr)) {
GST_ERROR_OBJECT (watcher, "Failed to start watcher, hr: 0x%x", (guint) hr);
g_mutex_unlock (&priv->lock);
return FALSE;
}
priv->running = TRUE;
g_mutex_unlock (&priv->lock);
return TRUE;
}
/**
* gst_winrt_device_watcher_stop:
* @device_class: a #GstWinRTDeviceClass to watch
*
* Stops watching device update.
*
* Since: 1.20
*/
void
gst_winrt_device_watcher_stop (GstWinRTDeviceWatcher * watcher)
{
GstWinRTDeviceWatcherPrivate *priv;
GstWinRTDeviceWatcherInner *inner;
HRESULT hr;
g_return_if_fail (GST_IS_WINRT_DEVICE_WATCHER (watcher));
GST_DEBUG_OBJECT (watcher, "Stop");
priv = watcher->priv;
inner = priv->inner;
g_mutex_lock (&priv->lock);
if (!priv->running) {
g_mutex_unlock (&priv->lock);
return;
}
priv->running = FALSE;
hr = inner->watcher->Stop ();
if (FAILED (hr)) {
GST_WARNING_OBJECT (watcher,
"Failed to stop watcher, hr: 0x%x", (guint) hr);
}
g_mutex_unlock (&priv->lock);
}