mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-04-21 21:52:03 +02:00

→ base 64 → Base64 cancelled → canceled {cannot, can't, couldn't} → unable to inexistent → nonexistent informations → information not possible → impossible retrieven → retrieved SIP try → attempt URI WebEngine wish → want Can this replace https://review.jami.net/c/jami-client-qt/+/27607 ? Change-Id: I21e1615a0c6e2979f02f913093c503c03ab32c82
1125 lines
31 KiB
C++
1125 lines
31 KiB
C++
/*
|
|
* Copyright (C) 2018-2024 Savoir-faire Linux Inc.
|
|
* Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
|
|
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "api/avmodel.h"
|
|
|
|
#include "api/video.h"
|
|
#include "api/lrc.h"
|
|
#ifdef ENABLE_LIBWRAP
|
|
#include "directrenderer.h"
|
|
#else
|
|
#include "shmrenderer.h"
|
|
#include <csignal>
|
|
#include <thread>
|
|
#endif
|
|
#include "callbackshandler.h"
|
|
#include "dbus/callmanager.h"
|
|
#include "dbus/configurationmanager.h"
|
|
#include "dbus/videomanager.h"
|
|
#include "authority/storagehelper.h"
|
|
|
|
#include <media_const.h>
|
|
|
|
#include <QtCore/QStandardPaths>
|
|
#include <QtCore/QDir>
|
|
#include <QUrl>
|
|
#include <QSize>
|
|
#include <QReadWriteLock>
|
|
|
|
#include <algorithm> // std::sort
|
|
#include <chrono>
|
|
#include <iomanip> // for std::put_time
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
#if defined(Q_OS_UNIX) && !defined(__APPLE__)
|
|
#include <xcb/xcb.h>
|
|
#endif
|
|
#ifdef WIN32
|
|
#include "Windows.h"
|
|
#include <tchar.h>
|
|
#include <dwmapi.h>
|
|
#endif
|
|
|
|
namespace lrc {
|
|
|
|
using namespace api;
|
|
using namespace api::video;
|
|
using namespace lrc::video;
|
|
|
|
class AVModelPimpl : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
AVModelPimpl(AVModel& linked, const CallbacksHandler& callbacksHandler);
|
|
|
|
const CallbacksHandler& callbacksHandler;
|
|
QString getRecordingPath() const;
|
|
static const QString recorderSavesSubdir;
|
|
AVModel& linked_;
|
|
|
|
QReadWriteLock renderersMutex_;
|
|
std::map<QString, std::unique_ptr<Renderer>> renderers_;
|
|
QString currentVideoCaptureDevice_ {};
|
|
|
|
#ifndef ENABLE_LIBWRAP
|
|
// TODO: Init Video Renderers from daemon (see:
|
|
// https://git.jami.net/savoirfairelinux/ring-daemon/issues/59)
|
|
static void stopCameraAndQuit(int);
|
|
static uint32_t SIZE_RENDERER;
|
|
#endif
|
|
|
|
/**
|
|
* Get device via its type
|
|
* @param type
|
|
* @return the device name
|
|
*/
|
|
QString getDevice(int type) const;
|
|
|
|
/**
|
|
* Add video::Renderer to renderers_ and start it
|
|
* @param id
|
|
* @param size
|
|
* @param shmPath
|
|
*/
|
|
void addRenderer(const QString& id, const QSize& res, const QString& shmPath = {});
|
|
|
|
/**
|
|
* Remove renderer from renderers_. If the returned renderer is ignored, it
|
|
* will be deleted.
|
|
* @param id
|
|
* @return the renderer
|
|
*/
|
|
std::unique_ptr<Renderer> removeRenderer(const QString& id);
|
|
|
|
bool hasRenderer(const QString& id);
|
|
QSize getRendererSize(const QString& id);
|
|
Frame getRendererFrame(const QString& id);
|
|
|
|
public Q_SLOTS:
|
|
/**
|
|
* Listen from CallbacksHandler when a renderer starts
|
|
* @param id
|
|
* @param shmPath
|
|
* @param width
|
|
* @param height
|
|
*/
|
|
void onDecodingStarted(const QString& id, const QString& shmPath, int width, int height);
|
|
/**
|
|
* Listen from CallbacksHandler when a renderer stops
|
|
* @param id
|
|
* @param shmPath
|
|
*/
|
|
void onDecodingStopped(const QString& id, const QString& shmPath);
|
|
/**
|
|
* Detect when a video device is plugged or unplugged
|
|
*/
|
|
void slotDeviceEvent();
|
|
/**
|
|
* Detect when an audio device is plugged or unplugged
|
|
*/
|
|
void slotAudioDeviceEvent();
|
|
/**
|
|
* Audio volume level
|
|
* @param id Ringbuffer id
|
|
* @param level Volume in range [0, 1]
|
|
*/
|
|
void slotAudioMeter(const QString& id, float level);
|
|
/**
|
|
* Listen from CallbacksHandler when a recorder stopped notice is incoming
|
|
* @param filePath
|
|
*/
|
|
void slotRecordPlaybackStopped(const QString& filePath);
|
|
};
|
|
|
|
const QString AVModelPimpl::recorderSavesSubdir = "sent_data";
|
|
#ifndef ENABLE_LIBWRAP
|
|
uint32_t AVModelPimpl::SIZE_RENDERER = 0;
|
|
#endif
|
|
|
|
AVModel::AVModel(const CallbacksHandler& callbacksHandler)
|
|
: QObject(nullptr)
|
|
, pimpl_(std::make_unique<AVModelPimpl>(*this, callbacksHandler))
|
|
{
|
|
#ifndef ENABLE_LIBWRAP
|
|
// Because the client uses DBUS, if a crash occurs, the daemon will not
|
|
// be able to know it. So, stop the camera if the user was just previewing.
|
|
std::signal(SIGSEGV, AVModelPimpl::stopCameraAndQuit);
|
|
std::signal(SIGINT, AVModelPimpl::stopCameraAndQuit);
|
|
#endif
|
|
}
|
|
|
|
AVModel::~AVModel()
|
|
{
|
|
QWriteLocker lk(&pimpl_->renderersMutex_);
|
|
for (auto r = pimpl_->renderers_.begin(); r != pimpl_->renderers_.end(); ++r) {
|
|
(*r).second.reset();
|
|
}
|
|
}
|
|
|
|
QList<MapStringString>
|
|
AVModel::getRenderersInfo(QString id)
|
|
{
|
|
QList<MapStringString> infoList;
|
|
QReadLocker lk(&pimpl_->renderersMutex_);
|
|
for (auto r = pimpl_->renderers_.begin(); r != pimpl_->renderers_.end(); r++) {
|
|
MapStringString qmap;
|
|
auto& rend = r->second;
|
|
MapStringString mapInfo = rend->getInfos();
|
|
if (id.isEmpty() || mapInfo["RENDERER_ID"] == id) {
|
|
qmap.insert(rend->RES, mapInfo["RES"]);
|
|
qmap.insert(rend->RENDERER_ID, mapInfo["RENDERER_ID"]);
|
|
qmap.insert(rend->FPS, mapInfo["FPS"]);
|
|
infoList.append(qmap);
|
|
}
|
|
}
|
|
return infoList;
|
|
return {};
|
|
}
|
|
|
|
void
|
|
AVModel::updateRenderersFPSInfo(QString rendererId)
|
|
{
|
|
QReadLocker lk(&pimpl_->renderersMutex_);
|
|
auto it = std::find_if(pimpl_->renderers_.begin(),
|
|
pimpl_->renderers_.end(),
|
|
[&rendererId](const auto& c) {
|
|
return rendererId == c.second->getInfos()["RENDERER_ID"];
|
|
});
|
|
if (it != pimpl_->renderers_.end()) {
|
|
auto fpsInfo = qMakePair(rendererId, it->second->getInfos()["FPS"]);
|
|
lk.unlock();
|
|
Q_EMIT onRendererFpsChange(fpsInfo);
|
|
}
|
|
}
|
|
|
|
bool
|
|
AVModel::getDecodingAccelerated() const
|
|
{
|
|
bool result = VideoManager::instance().getDecodingAccelerated();
|
|
return result;
|
|
}
|
|
|
|
void
|
|
AVModel::setDecodingAccelerated(bool accelerate)
|
|
{
|
|
VideoManager::instance().setDecodingAccelerated(accelerate);
|
|
}
|
|
|
|
bool
|
|
AVModel::getEncodingAccelerated() const
|
|
{
|
|
bool result = VideoManager::instance().getEncodingAccelerated();
|
|
return result;
|
|
}
|
|
|
|
void
|
|
AVModel::setEncodingAccelerated(bool accelerate)
|
|
{
|
|
VideoManager::instance().setEncodingAccelerated(accelerate);
|
|
}
|
|
|
|
bool
|
|
AVModel::getHardwareAcceleration() const
|
|
{
|
|
bool result = getDecodingAccelerated() && getEncodingAccelerated();
|
|
return result;
|
|
}
|
|
void
|
|
AVModel::setHardwareAcceleration(bool accelerate)
|
|
{
|
|
setDecodingAccelerated(accelerate);
|
|
setEncodingAccelerated(accelerate);
|
|
}
|
|
|
|
QVector<QString>
|
|
AVModel::getDevices() const
|
|
{
|
|
QStringList devices = VideoManager::instance().getDeviceList();
|
|
VectorString result;
|
|
for (const auto& device : devices) {
|
|
result.push_back(device);
|
|
}
|
|
return (QVector<QString>) result;
|
|
}
|
|
|
|
QString
|
|
AVModel::getDefaultDevice() const
|
|
{
|
|
return VideoManager::instance().getDefaultDevice();
|
|
}
|
|
|
|
void
|
|
AVModel::setDefaultDevice(const QString& deviceId)
|
|
{
|
|
VideoManager::instance().setDefaultDevice(deviceId);
|
|
}
|
|
|
|
Settings
|
|
AVModel::getDeviceSettings(const QString& deviceId) const
|
|
{
|
|
if (deviceId.isEmpty()) {
|
|
return video::Settings();
|
|
}
|
|
MapStringString settings = VideoManager::instance().getSettings(deviceId);
|
|
if (settings["id"] != deviceId) {
|
|
throw std::out_of_range("Device '" + deviceId.toStdString() + "' not found");
|
|
}
|
|
video::Settings result;
|
|
result.name = settings["name"];
|
|
result.id = settings["id"];
|
|
result.channel = settings["channel"];
|
|
result.size = settings["size"];
|
|
result.rate = settings["rate"].toFloat();
|
|
return result;
|
|
}
|
|
|
|
Capabilities
|
|
AVModel::getDeviceCapabilities(const QString& deviceId) const
|
|
{
|
|
// Channel x Resolution x Framerate
|
|
QMap<QString, QMap<QString, QVector<QString>>> capabilites = VideoManager::instance()
|
|
.getCapabilities(deviceId);
|
|
video::Capabilities result;
|
|
for (auto& channel : capabilites.toStdMap()) {
|
|
video::ResRateList channelCapabilities;
|
|
for (auto& resToRates : channel.second.toStdMap()) {
|
|
video::FrameratesList rates;
|
|
QVectorIterator<QString> itRates(resToRates.second);
|
|
while (itRates.hasNext()) {
|
|
rates.push_back(itRates.next().toFloat());
|
|
}
|
|
std::sort(rates.begin(), rates.end(), std::greater<int>());
|
|
channelCapabilities.push_back(qMakePair(resToRates.first, rates));
|
|
}
|
|
// sort by resolution widths
|
|
std::sort(channelCapabilities.begin(),
|
|
channelCapabilities.end(),
|
|
[](const QPair<video::Resolution, video::FrameratesList>& lhs,
|
|
const QPair<video::Resolution, video::FrameratesList>& rhs) {
|
|
auto lhsWidth = lhs.first.left(lhs.first.indexOf("x")).toLongLong();
|
|
auto rhsWidth = rhs.first.left(rhs.first.indexOf("x")).toLongLong();
|
|
return lhsWidth > rhsWidth;
|
|
});
|
|
result.insert(channel.first, channelCapabilities);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
AVModel::setDeviceSettings(video::Settings& settings)
|
|
{
|
|
MapStringString newSettings;
|
|
auto rate = QString::number(static_cast<double>(settings.rate), 'f', 7);
|
|
rate = rate.left(rate.length() - 1);
|
|
newSettings["channel"] = settings.channel;
|
|
newSettings["name"] = settings.name;
|
|
newSettings["id"] = settings.id;
|
|
newSettings["rate"] = rate;
|
|
newSettings["size"] = settings.size;
|
|
VideoManager::instance().applySettings(settings.id, newSettings);
|
|
|
|
// If the preview is running, reload it
|
|
// doing this during a call will cause re-invite, this is unwanted
|
|
QReadLocker lk(&pimpl_->renderersMutex_);
|
|
auto it = pimpl_->renderers_.find(video::PREVIEW_RENDERER_ID);
|
|
if (it != pimpl_->renderers_.end() && it->second && pimpl_->renderers_.size() == 1) {
|
|
lk.unlock();
|
|
stopPreview(video::PREVIEW_RENDERER_ID);
|
|
startPreview(video::PREVIEW_RENDERER_ID);
|
|
}
|
|
}
|
|
|
|
QString
|
|
AVModel::getDeviceIdFromName(const QString& deviceName) const
|
|
{
|
|
auto devices = getDevices();
|
|
auto iter = std::find_if(devices.begin(), devices.end(), [this, deviceName](const QString& d) {
|
|
auto settings = getDeviceSettings(d);
|
|
return settings.name == deviceName;
|
|
});
|
|
if (iter == devices.end()) {
|
|
LC_WARN << "Unable to find device: " << deviceName;
|
|
return {};
|
|
}
|
|
return *iter;
|
|
}
|
|
|
|
VectorString
|
|
AVModel::getSupportedAudioManagers() const
|
|
{
|
|
QStringList managers = ConfigurationManager::instance().getSupportedAudioManagers();
|
|
VectorString result;
|
|
for (const auto& manager : managers) {
|
|
result.push_back(manager);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString
|
|
AVModel::getAudioManager() const
|
|
{
|
|
return ConfigurationManager::instance().getAudioManager();
|
|
}
|
|
|
|
QVector<QString>
|
|
AVModel::getAudioOutputDevices() const
|
|
{
|
|
QStringList devices = ConfigurationManager::instance().getAudioOutputDeviceList();
|
|
|
|
// A fix for ring-daemon#43
|
|
if (ConfigurationManager::instance().getAudioManager() == QStringLiteral("pulseaudio")) {
|
|
if (devices.at(0) == QStringLiteral("default")) {
|
|
devices[0] = QObject::tr("default");
|
|
}
|
|
}
|
|
|
|
VectorString result;
|
|
for (const auto& device : devices) {
|
|
result.push_back(device);
|
|
}
|
|
return (QVector<QString>) result;
|
|
}
|
|
|
|
QVector<QString>
|
|
AVModel::getAudioInputDevices() const
|
|
{
|
|
QStringList devices = ConfigurationManager::instance().getAudioInputDeviceList();
|
|
|
|
// A fix for ring-daemon#43
|
|
if (ConfigurationManager::instance().getAudioManager() == QStringLiteral("pulseaudio")) {
|
|
if (devices.at(0) == QStringLiteral("default")) {
|
|
devices[0] = QObject::tr("default");
|
|
}
|
|
}
|
|
|
|
VectorString result;
|
|
for (const auto& device : devices) {
|
|
result.push_back(device);
|
|
}
|
|
return (QVector<QString>) result;
|
|
}
|
|
|
|
QString
|
|
AVModel::getRingtoneDevice() const
|
|
{
|
|
const int RINGTONE_IDX = 2;
|
|
return pimpl_->getDevice(RINGTONE_IDX);
|
|
}
|
|
|
|
QString
|
|
AVModel::getOutputDevice() const
|
|
{
|
|
const int OUTPUT_IDX = 0;
|
|
return pimpl_->getDevice(OUTPUT_IDX);
|
|
}
|
|
|
|
QString
|
|
AVModel::getInputDevice() const
|
|
{
|
|
const int INPUT_IDX = 1;
|
|
return pimpl_->getDevice(INPUT_IDX);
|
|
}
|
|
|
|
bool
|
|
AVModel::isAudioMeterActive(const QString& id) const
|
|
{
|
|
return ConfigurationManager::instance().isAudioMeterActive(id);
|
|
}
|
|
|
|
void
|
|
AVModel::setAudioMeterState(bool active, const QString& id) const
|
|
{
|
|
ConfigurationManager::instance().setAudioMeterState(id, active);
|
|
}
|
|
|
|
void
|
|
AVModel::startAudioDevice() const
|
|
{
|
|
VideoManager::instance().startAudioDevice();
|
|
}
|
|
|
|
void
|
|
AVModel::stopAudioDevice() const
|
|
{
|
|
VideoManager::instance().stopAudioDevice();
|
|
}
|
|
|
|
bool
|
|
AVModel::setAudioManager(const QString& name)
|
|
{
|
|
return ConfigurationManager::instance().setAudioManager(name);
|
|
}
|
|
|
|
void
|
|
AVModel::setRingtoneDevice(int idx)
|
|
{
|
|
ConfigurationManager::instance().setAudioRingtoneDevice(idx);
|
|
}
|
|
|
|
void
|
|
AVModel::setOutputDevice(int idx)
|
|
{
|
|
ConfigurationManager::instance().setAudioOutputDevice(idx);
|
|
}
|
|
|
|
void
|
|
AVModel::setInputDevice(int idx)
|
|
{
|
|
ConfigurationManager::instance().setAudioInputDevice(idx);
|
|
}
|
|
|
|
void
|
|
AVModel::stopLocalRecorder(const QString& path) const
|
|
{
|
|
if (path.isEmpty()) {
|
|
LC_WARN << "stopLocalRecorder: unable to stop non existing recording";
|
|
return;
|
|
}
|
|
|
|
VideoManager::instance().stopLocalRecorder(path);
|
|
}
|
|
|
|
QString
|
|
AVModel::startLocalMediaRecorder(const QString& videoInputId) const
|
|
{
|
|
const QString path = pimpl_->getRecordingPath();
|
|
const QString finalPath = VideoManager::instance().startLocalMediaRecorder(videoInputId, path);
|
|
return finalPath;
|
|
}
|
|
|
|
QString
|
|
AVModel::getRecordPath() const
|
|
{
|
|
return ConfigurationManager::instance().getRecordPath();
|
|
}
|
|
|
|
void
|
|
AVModel::setRecordPath(const QString& path) const
|
|
{
|
|
ConfigurationManager::instance().setRecordPath(path.toUtf8());
|
|
}
|
|
|
|
bool
|
|
AVModel::getAlwaysRecord() const
|
|
{
|
|
return ConfigurationManager::instance().getIsAlwaysRecording();
|
|
}
|
|
|
|
void
|
|
AVModel::setAlwaysRecord(const bool& rec) const
|
|
{
|
|
ConfigurationManager::instance().setIsAlwaysRecording(rec);
|
|
}
|
|
|
|
bool
|
|
AVModel::getRecordPreview() const
|
|
{
|
|
return ConfigurationManager::instance().getRecordPreview();
|
|
}
|
|
|
|
void
|
|
AVModel::setRecordPreview(const bool& rec) const
|
|
{
|
|
ConfigurationManager::instance().setRecordPreview(rec);
|
|
}
|
|
|
|
int
|
|
AVModel::getRecordQuality() const
|
|
{
|
|
return ConfigurationManager::instance().getRecordQuality();
|
|
}
|
|
|
|
void
|
|
AVModel::setRecordQuality(const int& rec) const
|
|
{
|
|
ConfigurationManager::instance().setRecordQuality(rec);
|
|
}
|
|
|
|
QString
|
|
AVModel::startPreview(const QString& resource)
|
|
{
|
|
return VideoManager::instance().openVideoInput(resource);
|
|
}
|
|
|
|
bool
|
|
AVModel::stopPreview(const QString& resource)
|
|
{
|
|
return VideoManager::instance().closeVideoInput(resource);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
BOOL
|
|
IsAltTabWindow(HWND hwnd)
|
|
{
|
|
auto styles = (DWORD) GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
auto ex_styles = (DWORD) GetWindowLongPtr(hwnd, GWL_EXSTYLE);
|
|
|
|
if (ex_styles & WS_EX_TOOLWINDOW)
|
|
return false;
|
|
if (styles & WS_CHILD)
|
|
return false;
|
|
|
|
DWORD cloaked = FALSE;
|
|
HRESULT hrTemp = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
|
|
if (SUCCEEDED(hrTemp) && cloaked == DWM_CLOAKED_SHELL) {
|
|
return false;
|
|
}
|
|
|
|
// Start at the root owner
|
|
HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);
|
|
|
|
// See if we are the last active visible popup
|
|
HWND hwndTry;
|
|
while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
|
|
if (IsWindowVisible(hwndTry))
|
|
break;
|
|
hwndWalk = hwndTry;
|
|
}
|
|
return hwndWalk == hwnd;
|
|
}
|
|
|
|
BOOL CALLBACK
|
|
CbEnumAltTab(HWND hwnd, LPARAM lParam)
|
|
{
|
|
const size_t MAX_WINDOW_NAME = 256;
|
|
TCHAR windowName[MAX_WINDOW_NAME];
|
|
GetWindowText(hwnd, windowName, MAX_WINDOW_NAME);
|
|
|
|
// Do not show windows that has no caption
|
|
if (0 == windowName[0])
|
|
return TRUE;
|
|
|
|
std::wstring msg = std::wstring(windowName);
|
|
auto name = QString::fromStdWString(msg);
|
|
|
|
// Do not show invisible windows
|
|
if (!IsWindowVisible(hwnd))
|
|
return TRUE;
|
|
|
|
// Alt-tab test as described by Raymond Chen
|
|
if (!IsAltTabWindow(hwnd))
|
|
return TRUE;
|
|
|
|
auto isShellWindow = hwnd == GetShellWindow();
|
|
|
|
if (isShellWindow)
|
|
return TRUE;
|
|
|
|
QMap<QString, QVariant>* windowList = reinterpret_cast<QMap<QString, QVariant>*>(lParam);
|
|
auto keys = windowList->keys();
|
|
if (keys.indexOf(name) > 0) {
|
|
return FALSE;
|
|
} else {
|
|
std::stringstream ss;
|
|
ss << hwnd;
|
|
windowList->insert(name, QString::fromStdString(ss.str()));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
#if defined(Q_OS_UNIX) && !defined(__APPLE__)
|
|
static xcb_atom_t
|
|
getAtom(xcb_connection_t* c, const std::string& atomName)
|
|
{
|
|
xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(c, 0, atomName.size(), atomName.c_str());
|
|
if (auto* rep = xcb_intern_atom_reply(c, atom_cookie, nullptr)) {
|
|
xcb_atom_t atom = rep->atom;
|
|
free(rep);
|
|
return atom;
|
|
}
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
const QVariantMap
|
|
AVModel::getListWindows() const
|
|
{
|
|
QMap<QString, QVariant> ret {};
|
|
|
|
#if defined(Q_OS_UNIX) && !defined(__APPLE__)
|
|
std::unique_ptr<xcb_connection_t, void (*)(xcb_connection_t*)> c(xcb_connect(nullptr, nullptr),
|
|
[](xcb_connection_t* ptr) {
|
|
xcb_disconnect(ptr);
|
|
});
|
|
|
|
if (xcb_connection_has_error(c.get())) {
|
|
LC_DBG << "xcb connection has error";
|
|
return ret;
|
|
}
|
|
|
|
auto atomNetClient = getAtom(c.get(), "_NET_CLIENT_LIST");
|
|
auto atomWMVisibleName = getAtom(c.get(), "_NET_WM_NAME");
|
|
if (!atomNetClient || !atomWMVisibleName)
|
|
return ret;
|
|
|
|
auto* screen = xcb_setup_roots_iterator(xcb_get_setup(c.get())).data;
|
|
|
|
xcb_get_property_cookie_t propCookieList = xcb_get_property(c.get(),
|
|
0,
|
|
screen->root,
|
|
atomNetClient,
|
|
XCB_GET_PROPERTY_TYPE_ANY,
|
|
0,
|
|
100);
|
|
|
|
using propertyPtr
|
|
= std::unique_ptr<xcb_get_property_reply_t, void (*)(xcb_get_property_reply_t*)>;
|
|
|
|
xcb_generic_error_t* e;
|
|
propertyPtr replyPropList(xcb_get_property_reply(c.get(), propCookieList, &e),
|
|
[](auto* ptr) { free(ptr); });
|
|
if (e) {
|
|
LC_DBG << "Error: " << e->error_code;
|
|
free(e);
|
|
}
|
|
if (replyPropList.get()) {
|
|
int valueLegth = xcb_get_property_value_length(replyPropList.get());
|
|
if (valueLegth) {
|
|
auto* win = static_cast<xcb_window_t*>(xcb_get_property_value(replyPropList.get()));
|
|
for (int i = 0; i < valueLegth / 4; i++) {
|
|
xcb_get_property_cookie_t prop_cookie = xcb_get_property(c.get(),
|
|
0,
|
|
win[i],
|
|
atomWMVisibleName,
|
|
XCB_GET_PROPERTY_TYPE_ANY,
|
|
0,
|
|
1000);
|
|
propertyPtr replyProp {xcb_get_property_reply(c.get(), prop_cookie, &e),
|
|
[](auto* ptr) {
|
|
free(ptr);
|
|
}};
|
|
if (e) {
|
|
LC_DBG << "Error: " << e->error_code;
|
|
free(e);
|
|
}
|
|
if (replyProp.get()) {
|
|
int v_size = xcb_get_property_value_length(replyProp.get());
|
|
if (v_size) {
|
|
auto v = std::string(reinterpret_cast<char*>(
|
|
xcb_get_property_value(replyProp.get())),
|
|
v_size);
|
|
auto name = QString::fromUtf8(v.c_str());
|
|
if (ret.find(name) != ret.end())
|
|
name += QString(" - 0x%1").arg(win[i], 0, 16);
|
|
ret.insert(name, QVariant(QString("0x%1").arg(win[i], 0, 16)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef WIN32
|
|
try {
|
|
auto newWindow = true;
|
|
LPARAM lParam = reinterpret_cast<LPARAM>(&ret);
|
|
while (newWindow) {
|
|
newWindow = EnumWindows(CbEnumAltTab, lParam);
|
|
}
|
|
auto finishedloop = true;
|
|
} catch (...) {
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
AVModel::setCurrentVideoCaptureDevice(const QString& currentVideoCaptureDevice)
|
|
{
|
|
pimpl_->currentVideoCaptureDevice_ = currentVideoCaptureDevice;
|
|
}
|
|
|
|
QString
|
|
AVModel::getCurrentVideoCaptureDevice() const
|
|
{
|
|
return pimpl_->currentVideoCaptureDevice_;
|
|
}
|
|
|
|
void
|
|
AVModel::clearCurrentVideoCaptureDevice()
|
|
{
|
|
pimpl_->currentVideoCaptureDevice_.clear();
|
|
}
|
|
|
|
void
|
|
AVModel::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
|
|
{
|
|
pimpl_->addRenderer(id, res, shmPath);
|
|
}
|
|
|
|
bool
|
|
AVModel::hasRenderer(const QString& id)
|
|
{
|
|
return pimpl_->hasRenderer(id);
|
|
}
|
|
|
|
QSize
|
|
AVModel::getRendererSize(const QString& id)
|
|
{
|
|
return pimpl_->getRendererSize(id);
|
|
}
|
|
|
|
Frame
|
|
AVModel::getRendererFrame(const QString& id)
|
|
{
|
|
return pimpl_->getRendererFrame(id);
|
|
}
|
|
|
|
bool
|
|
AVModel::useDirectRenderer() const
|
|
{
|
|
#ifdef ENABLE_LIBWRAP
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
QString
|
|
AVModel::createMediaPlayer(const QString& resource)
|
|
{
|
|
return VideoManager::instance().createMediaPlayer(resource);
|
|
}
|
|
|
|
void
|
|
AVModel::closeMediaPlayer(const QString& resource)
|
|
{
|
|
VideoManager::instance().closeMediaPlayer(resource);
|
|
}
|
|
|
|
bool
|
|
AVModel::pausePlayer(const QString& id, bool pause)
|
|
{
|
|
return VideoManager::instance().pausePlayer(id, pause);
|
|
}
|
|
|
|
bool
|
|
AVModel::mutePlayerAudio(const QString& id, bool mute)
|
|
{
|
|
return VideoManager::instance().mutePlayerAudio(id, mute);
|
|
}
|
|
|
|
bool
|
|
AVModel::playerSeekToTime(const QString& id, int time)
|
|
{
|
|
return VideoManager::instance().playerSeekToTime(id, time);
|
|
}
|
|
|
|
qint64
|
|
AVModel::getPlayerPosition(const QString& id)
|
|
{
|
|
return VideoManager::instance().getPlayerPosition(id);
|
|
}
|
|
|
|
qint64
|
|
AVModel::getPlayerDuration(const QString& id)
|
|
{
|
|
return VideoManager::instance().getPlayerDuration(id);
|
|
}
|
|
|
|
void
|
|
AVModel::setAutoRestart(const QString& id, bool restart)
|
|
{
|
|
VideoManager::instance().setAutoRestart(id, restart);
|
|
}
|
|
|
|
AVModelPimpl::AVModelPimpl(AVModel& linked, const CallbacksHandler& callbacksHandler)
|
|
: callbacksHandler(callbacksHandler)
|
|
, linked_(linked)
|
|
{
|
|
std::srand(std::time(nullptr));
|
|
#ifndef ENABLE_LIBWRAP
|
|
SIZE_RENDERER = renderers_.size();
|
|
#endif
|
|
connect(&callbacksHandler, &CallbacksHandler::deviceEvent, this, &AVModelPimpl::slotDeviceEvent);
|
|
connect(&callbacksHandler,
|
|
&CallbacksHandler::audioDeviceEvent,
|
|
this,
|
|
&AVModelPimpl::slotAudioDeviceEvent);
|
|
connect(&callbacksHandler, &CallbacksHandler::audioMeter, this, &AVModelPimpl::slotAudioMeter);
|
|
connect(&callbacksHandler,
|
|
&CallbacksHandler::recordPlaybackStopped,
|
|
this,
|
|
&AVModelPimpl::slotRecordPlaybackStopped);
|
|
|
|
// render connections
|
|
connect(&callbacksHandler,
|
|
&CallbacksHandler::decodingStarted,
|
|
this,
|
|
&AVModelPimpl::onDecodingStarted,
|
|
Qt::DirectConnection);
|
|
connect(&callbacksHandler,
|
|
&CallbacksHandler::decodingStopped,
|
|
this,
|
|
&AVModelPimpl::onDecodingStopped,
|
|
Qt::DirectConnection);
|
|
|
|
// Media player connection
|
|
connect(&callbacksHandler,
|
|
&CallbacksHandler::fileOpened,
|
|
this,
|
|
[this](const QString& path, MapStringString info) {
|
|
Q_UNUSED(path);
|
|
bool hasAudio = info["audio_stream"].toInt() >= 0;
|
|
bool hasVideo = info["video_stream"].toInt() >= 0;
|
|
Q_EMIT linked_.fileOpened(hasAudio, hasVideo);
|
|
});
|
|
|
|
auto startedPreview = false;
|
|
auto restartRenderers = [&](const QStringList& callList) {
|
|
for (const auto& callId : callList) {
|
|
MapStringString rendererInfos = VideoManager::instance().getRenderer(callId);
|
|
auto shmPath = rendererInfos[libjami::Media::Details::SHM_PATH];
|
|
auto width = rendererInfos[libjami::Media::Details::WIDTH].toInt();
|
|
auto height = rendererInfos[libjami::Media::Details::HEIGHT].toInt();
|
|
if (width > 0 && height > 0) {
|
|
startedPreview = true;
|
|
onDecodingStarted(callId, shmPath, width, height);
|
|
}
|
|
}
|
|
};
|
|
restartRenderers(CallManager::instance().getCallList(""));
|
|
auto confIds = lrc::api::Lrc::getConferences();
|
|
QStringList list;
|
|
Q_FOREACH (QString confId, confIds) {
|
|
list << confId;
|
|
}
|
|
restartRenderers(list);
|
|
if (startedPreview)
|
|
restartRenderers({"local"});
|
|
currentVideoCaptureDevice_ = VideoManager::instance().getDefaultDevice();
|
|
}
|
|
|
|
QString
|
|
AVModelPimpl::getRecordingPath() const
|
|
{
|
|
const QDir dir = authority::storage::getPath() + "/" + recorderSavesSubdir;
|
|
dir.mkpath(".");
|
|
|
|
std::chrono::time_point<std::chrono::system_clock> time_now = std::chrono::system_clock::now();
|
|
std::time_t time_now_t = std::chrono::system_clock::to_time_t(time_now);
|
|
std::tm now_tm = *std::localtime(&time_now_t);
|
|
|
|
std::stringstream ss;
|
|
ss << dir.path().toStdString();
|
|
ss << "/";
|
|
ss << std::put_time(&now_tm, "%Y%m%d-%H%M%S");
|
|
ss << "-";
|
|
ss << std::rand();
|
|
|
|
QDir file_path(ss.str().c_str());
|
|
|
|
return file_path.path();
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::onDecodingStarted(const QString& id, const QString& shmPath, int width, int height)
|
|
{
|
|
addRenderer(id, QSize(width, height), shmPath);
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::onDecodingStopped(const QString& id, const QString& shmPath)
|
|
{
|
|
Q_UNUSED(shmPath)
|
|
removeRenderer(id);
|
|
}
|
|
|
|
#ifndef ENABLE_LIBWRAP
|
|
void
|
|
AVModelPimpl::stopCameraAndQuit(int)
|
|
{
|
|
if (SIZE_RENDERER == 1) {
|
|
// This will stop the preview if needed (not in a call).
|
|
VideoManager::instance().closeVideoInput(PREVIEW_RENDERER_ID);
|
|
// HACK: this sleep is just here to let the camera stop and
|
|
// avoid immediate raise
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
std::raise(SIGTERM);
|
|
}
|
|
|
|
#endif
|
|
QString
|
|
AVModelPimpl::getDevice(int type) const
|
|
{
|
|
if (type < 0 || type > 2)
|
|
return {}; // No device
|
|
QString result;
|
|
VectorString devices;
|
|
switch (type) {
|
|
case 1: // INPUT
|
|
devices = linked_.getAudioInputDevices();
|
|
break;
|
|
case 0: // OUTPUT
|
|
case 2: // RINGTONE
|
|
devices = linked_.getAudioOutputDevices();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
QStringList currentDevicesIdx = ConfigurationManager::instance().getCurrentAudioDevicesIndex();
|
|
try {
|
|
// Should not happen, but cannot retrieve current ringtone device
|
|
if (currentDevicesIdx.size() < 3)
|
|
return "";
|
|
auto deviceIdx = currentDevicesIdx[type].toInt();
|
|
if (deviceIdx < devices.size())
|
|
result = devices.at(deviceIdx);
|
|
} catch (std::bad_alloc& ba) {
|
|
LC_WARN << "bad_alloc caught: " << ba.what();
|
|
return "";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static std::unique_ptr<Renderer>
|
|
createRenderer(const QString& id, const QSize& res, const QString& shmPath = {})
|
|
{
|
|
#ifdef ENABLE_LIBWRAP
|
|
Q_UNUSED(shmPath)
|
|
return std::make_unique<DirectRenderer>(id, res);
|
|
#else
|
|
return std::make_unique<ShmRenderer>(id, res, shmPath);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
|
|
{
|
|
// Remove the renderer if it already exists.
|
|
std::ignore = removeRenderer(id);
|
|
|
|
{
|
|
QWriteLocker lk(&renderersMutex_);
|
|
renderers_[id] = createRenderer(id, res, shmPath);
|
|
}
|
|
|
|
QReadLocker lk(&renderersMutex_);
|
|
if (auto* renderer = renderers_.find(id)->second.get()) {
|
|
connect(
|
|
renderer,
|
|
&Renderer::fpsChanged,
|
|
this,
|
|
[this, id](void) { linked_.updateRenderersFPSInfo(id); },
|
|
Qt::QueuedConnection);
|
|
connect(
|
|
renderer,
|
|
&Renderer::started,
|
|
this,
|
|
[this, id](const QSize& size) { Q_EMIT linked_.rendererStarted(id, size); },
|
|
Qt::DirectConnection);
|
|
#ifdef ENABLE_LIBWRAP
|
|
connect(
|
|
renderer,
|
|
&Renderer::frameBufferRequested,
|
|
this,
|
|
[this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); },
|
|
Qt::DirectConnection);
|
|
#endif
|
|
connect(
|
|
renderer,
|
|
&Renderer::frameUpdated,
|
|
this,
|
|
[this, id] { Q_EMIT linked_.frameUpdated(id); },
|
|
Qt::DirectConnection);
|
|
connect(
|
|
renderer,
|
|
&Renderer::stopped,
|
|
this,
|
|
[this, id] { Q_EMIT linked_.rendererStopped(id); },
|
|
Qt::DirectConnection);
|
|
|
|
renderer->startRendering();
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<Renderer>
|
|
AVModelPimpl::removeRenderer(const QString& id)
|
|
{
|
|
QWriteLocker lk(&renderersMutex_);
|
|
auto it = renderers_.find(id);
|
|
if (it == renderers_.end()) {
|
|
LC_DBG << "Unable to remove renderer. " << id << " not found";
|
|
return {};
|
|
}
|
|
auto removed = std::move(it->second);
|
|
renderers_.erase(it);
|
|
return removed;
|
|
}
|
|
|
|
bool
|
|
AVModelPimpl::hasRenderer(const QString& id)
|
|
{
|
|
QReadLocker lk(&renderersMutex_);
|
|
return renderers_.find(id) != renderers_.end();
|
|
}
|
|
|
|
QSize
|
|
AVModelPimpl::getRendererSize(const QString& id)
|
|
{
|
|
QReadLocker lk(&renderersMutex_);
|
|
auto it = renderers_.find(id);
|
|
if (it != renderers_.end()) {
|
|
return it->second->size();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
Frame
|
|
AVModelPimpl::getRendererFrame(const QString& id)
|
|
{
|
|
QReadLocker lk(&renderersMutex_);
|
|
auto it = renderers_.find(id);
|
|
if (it != renderers_.end()) {
|
|
return it->second->currentFrame();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::slotDeviceEvent()
|
|
{
|
|
Q_EMIT linked_.deviceEvent();
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::slotAudioDeviceEvent()
|
|
{
|
|
Q_EMIT linked_.audioDeviceEvent();
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::slotAudioMeter(const QString& id, float level)
|
|
{
|
|
Q_EMIT linked_.audioMeter(id, level);
|
|
}
|
|
|
|
void
|
|
AVModelPimpl::slotRecordPlaybackStopped(const QString& filePath)
|
|
{
|
|
Q_EMIT linked_.recordPlaybackStopped(filePath);
|
|
}
|
|
|
|
} // namespace lrc
|
|
|
|
#include "api/moc_avmodel.cpp"
|
|
#include "avmodel.moc"
|