blob: 59aa91b03bea5fd5e12042bb7e25940e45892959 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwinrtvideodeviceselectorcontrol.h"
#include <QtCore/qfunctions_winrt.h>
#include <QtCore/QVector>
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QGlobalStatic>
#include <wrl.h>
#include <windows.devices.enumeration.h>
using namespace ABI::Windows::Devices::Enumeration;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
typedef ITypedEventHandler<DeviceWatcher *, DeviceInformation *> DeviceInformationHandler;
typedef ITypedEventHandler<DeviceWatcher *, DeviceInformationUpdate *> DeviceInformationUpdateHandler;
typedef ITypedEventHandler<DeviceWatcher *, IInspectable *> DeviceEnumerationCompletedHandler;
QT_BEGIN_NAMESPACE
static QString deviceName(IDeviceInformation *device)
{
HRESULT hr;
HString id;
hr = device->get_Id(id.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
quint32 length;
const wchar_t *buffer = id.GetRawBuffer(&length);
return QString::fromWCharArray(buffer, int(length));
}
static QString deviceDescription(IDeviceInformation *device)
{
HRESULT hr;
HString name;
hr = device->get_Name(name.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
quint32 length;
const wchar_t *buffer = name.GetRawBuffer(&length);
return QString::fromWCharArray(buffer, int(length));
}
struct QWinRTVideoDeviceSelectorControlGlobal
{
QWinRTVideoDeviceSelectorControlGlobal()
: defaultDeviceIndex(-1)
{
HRESULT hr;
ComPtr<IDeviceInformationStatics> deviceWatcherFactory;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(),
IID_PPV_ARGS(&deviceWatcherFactory));
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcherFactory->CreateWatcherDeviceClass(DeviceClass_VideoCapture, &deviceWatcher);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcher->add_Added(
Callback<DeviceInformationHandler>(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceAdded).Get(),
&deviceAddedToken);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcher->add_Removed(
Callback<DeviceInformationUpdateHandler>(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceRemoved).Get(),
&deviceRemovedToken);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcher->add_Updated(
Callback<DeviceInformationUpdateHandler>(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceUpdated).Get(),
&deviceUpdatedToken);
Q_ASSERT_SUCCEEDED(hr);
// Synchronously populate the devices on construction
ComPtr<IAsyncOperation<DeviceInformationCollection *>> op;
hr = deviceWatcherFactory->FindAllAsyncDeviceClass(DeviceClass_VideoCapture, &op);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IVectorView<DeviceInformation *>> deviceList;
hr = QWinRTFunctions::await(op, deviceList.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
quint32 deviceCount;
hr = deviceList->get_Size(&deviceCount);
Q_ASSERT_SUCCEEDED(hr);
for (quint32 i = 0; i < deviceCount; ++i) {
IDeviceInformation *device;
hr = deviceList->GetAt(i, &device);
Q_ASSERT_SUCCEEDED(hr);
onDeviceAdded(nullptr, device);
}
// If there is no default device provided by the API, choose the first one
if (!devices.isEmpty() && defaultDeviceIndex < 0)
defaultDeviceIndex = 0;
}
~QWinRTVideoDeviceSelectorControlGlobal()
{
HRESULT hr;
hr = deviceWatcher->remove_Added(deviceAddedToken);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcher->remove_Removed(deviceRemovedToken);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceWatcher->remove_Updated(deviceUpdatedToken);
Q_ASSERT_SUCCEEDED(hr);
}
private:
HRESULT onDeviceAdded(IDeviceWatcher *, IDeviceInformation *device)
{
const QString name = deviceName(device);
if (deviceIndex.contains(name))
return S_OK;
devices.append(device);
const int index = devices.size() - 1;
deviceIndex.insert(name, index);
HRESULT hr;
boolean isDefault;
hr = device->get_IsDefault(&isDefault);
Q_ASSERT_SUCCEEDED(hr);
if (isDefault)
defaultDeviceIndex = index;
for (QWinRTVideoDeviceSelectorControl *watcher : qAsConst(watchers))
emit watcher->devicesChanged();
return S_OK;
}
HRESULT onDeviceRemoved(IDeviceWatcher *, IDeviceInformationUpdate *device)
{
HRESULT hr;
HString id;
hr = device->get_Id(id.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
HString name;
hr = device->get_Id(name.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
quint32 nameLength;
const wchar_t *nameString = name.GetRawBuffer(&nameLength);
const int index = deviceIndex.take(QString::fromWCharArray(nameString, int(nameLength)));
if (index >= 0)
devices.remove(index);
for (QWinRTVideoDeviceSelectorControl *watcher : qAsConst(watchers))
emit watcher->devicesChanged();
return S_OK;
}
HRESULT onDeviceUpdated(IDeviceWatcher *, IDeviceInformationUpdate *)
{
// A name or description may have changed, so emit devicesChanged
for (QWinRTVideoDeviceSelectorControl *watcher : qAsConst(watchers))
emit watcher->devicesChanged();
return S_OK;
}
public:
void addWatcher(QWinRTVideoDeviceSelectorControl *control)
{
watchers.append(control);
HRESULT hr;
DeviceWatcherStatus status;
hr = deviceWatcher->get_Status(&status);
Q_ASSERT_SUCCEEDED(hr);
if (status != DeviceWatcherStatus_Started &&
status != DeviceWatcherStatus_EnumerationCompleted) {
// We can't immediately Start() if we have just called Stop()
while (status == DeviceWatcherStatus_Stopping) {
QThread::yieldCurrentThread();
hr = deviceWatcher->get_Status(&status);
Q_ASSERT_SUCCEEDED(hr);
}
hr = deviceWatcher->Start();
Q_ASSERT_SUCCEEDED(hr);
}
}
void removeWatcher(QWinRTVideoDeviceSelectorControl *control)
{
watchers.removeAll(control);
if (!watchers.isEmpty())
return;
HRESULT hr;
DeviceWatcherStatus status;
hr = deviceWatcher->get_Status(&status);
Q_ASSERT_SUCCEEDED(hr);
if (status == DeviceWatcherStatus_Stopped || status == DeviceWatcherStatus_Stopping)
return;
hr = deviceWatcher->Stop();
Q_ASSERT_SUCCEEDED(hr);
}
QVector<ComPtr<IDeviceInformation>> devices;
QHash<QString, int> deviceIndex;
int defaultDeviceIndex;
private:
ComPtr<IDeviceWatcher> deviceWatcher;
QList<QWinRTVideoDeviceSelectorControl *> watchers;
EventRegistrationToken deviceAddedToken;
EventRegistrationToken deviceRemovedToken;
EventRegistrationToken deviceUpdatedToken;
};
Q_GLOBAL_STATIC(QWinRTVideoDeviceSelectorControlGlobal, g)
static ComPtr<IEnclosureLocation> enclosureLocation(const QString &deviceName)
{
ComPtr<IEnclosureLocation> enclosureLocation;
int deviceIndex = g->deviceIndex.value(deviceName);
IDeviceInformation *deviceInfo = g->devices.value(deviceIndex).Get();
if (!deviceInfo)
return enclosureLocation;
HRESULT hr;
hr = deviceInfo->get_EnclosureLocation(&enclosureLocation);
if (FAILED(hr))
qErrnoWarning(hr, "Failed to get camera enclosure location");
return enclosureLocation;
}
class QWinRTVideoDeviceSelectorControlPrivate
{
public:
int selectedDevice;
};
QWinRTVideoDeviceSelectorControl::QWinRTVideoDeviceSelectorControl(QObject *parent)
: QVideoDeviceSelectorControl(parent), d_ptr(new QWinRTVideoDeviceSelectorControlPrivate)
{
Q_D(QWinRTVideoDeviceSelectorControl);
d->selectedDevice = -1;
g->addWatcher(this);
}
QWinRTVideoDeviceSelectorControl::~QWinRTVideoDeviceSelectorControl()
{
if (g.isDestroyed())
return;
g->removeWatcher(this);
}
int QWinRTVideoDeviceSelectorControl::deviceCount() const
{
return g->devices.size();
}
QString QWinRTVideoDeviceSelectorControl::deviceName(int index) const
{
if (index < 0 || index >= g->devices.size())
return QString();
return ::deviceName(g->devices.at(index).Get());
}
QString QWinRTVideoDeviceSelectorControl::deviceDescription(int index) const
{
if (index < 0 || index >= g->devices.size())
return QString();
return ::deviceDescription(g->devices.at(index).Get());
}
int QWinRTVideoDeviceSelectorControl::defaultDevice() const
{
return g->defaultDeviceIndex;
}
int QWinRTVideoDeviceSelectorControl::selectedDevice() const
{
Q_D(const QWinRTVideoDeviceSelectorControl);
return d->selectedDevice;
}
QCamera::Position QWinRTVideoDeviceSelectorControl::cameraPosition(const QString &deviceName)
{
ComPtr<IEnclosureLocation> enclosure = enclosureLocation(deviceName);
if (!enclosure)
return QCamera::UnspecifiedPosition;
HRESULT hr;
Panel panel;
hr = enclosure->get_Panel(&panel);
RETURN_IF_FAILED("Failed to get camera panel location", return QCamera::UnspecifiedPosition);
switch (panel) {
case Panel_Front:
return QCamera::FrontFace;
case Panel_Back:
return QCamera::BackFace;
default:
break;
}
return QCamera::UnspecifiedPosition;
}
int QWinRTVideoDeviceSelectorControl::cameraOrientation(const QString &deviceName)
{
ComPtr<IEnclosureLocation> enclosure = enclosureLocation(deviceName);
if (!enclosure)
return 0;
HRESULT hr;
ComPtr<IEnclosureLocation2> enclosure2;
hr = enclosure.As(&enclosure2);
RETURN_IF_FAILED("Failed to cast camera enclosure location", return 0);
quint32 rotation;
hr = enclosure2->get_RotationAngleInDegreesClockwise(&rotation);
RETURN_IF_FAILED("Failed to get camera rotation angle", return 0);
return int(rotation);
}
QList<QByteArray> QWinRTVideoDeviceSelectorControl::deviceNames()
{
QList<QByteArray> devices;
devices.reserve(g->deviceIndex.size());
for (auto it = g->deviceIndex.keyBegin(), end = g->deviceIndex.keyEnd(); it != end; ++it)
devices.append((*it).toUtf8());
return devices;
}
QByteArray QWinRTVideoDeviceSelectorControl::deviceDescription(const QByteArray &deviceName)
{
int deviceIndex = g->deviceIndex.value(QString::fromUtf8(deviceName), -1);
if (deviceIndex < 0)
return QByteArray();
return ::deviceDescription(g->devices.value(deviceIndex).Get()).toUtf8();
}
QByteArray QWinRTVideoDeviceSelectorControl::defaultDeviceName()
{
if (g->defaultDeviceIndex < 0)
return QByteArray();
return ::deviceName(g->devices.value(g->defaultDeviceIndex).Get()).toUtf8();
}
void QWinRTVideoDeviceSelectorControl::setSelectedDevice(int index)
{
Q_D(QWinRTVideoDeviceSelectorControl);
int selectedDevice = index;
if (index < 0 || index >= g->devices.size())
selectedDevice = -1;
if (d->selectedDevice != selectedDevice) {
d->selectedDevice = selectedDevice;
emit selectedDeviceChanged(d->selectedDevice);
emit selectedDeviceChanged(deviceName(d->selectedDevice));
}
}
QT_END_NAMESPACE