Compare commits

...

1 Commits

Author SHA1 Message Date
Tobias Fella
3094ea20ae Call stuff 2021-11-06 14:38:38 +01:00
23 changed files with 774 additions and 1 deletions

View File

@@ -114,6 +114,9 @@ find_package(QCoro REQUIRED)
qcoro_enable_coroutines()
include(FindPkgConfig)
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16)
if(ANDROID)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
endif()

0
LICENSES/MIT.txt Executable file → Normal file
View File

View File

@@ -37,6 +37,16 @@ add_executable(neochat
blurhash.cpp
blurhashimageprovider.cpp
joinrulesevent.cpp
voip/audiosources.cpp
voip/devicemonitor.cpp
voip/pipelinemanager.cpp
voip/screencast.cpp
voip/videosources.cpp
voip/waylandscreencast.cpp
voip/webrtcfoo.cpp
voip/windowmodel.cpp
voip/xscreencast.cpp
voip/callhandler.cpp
../res.qrc
)
@@ -61,7 +71,7 @@ if(NOT ANDROID)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat PRIVATE Qt::Quick Qt::Qml Qt::Gui Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::QCoro)
target_link_libraries(neochat PRIVATE Qt::Quick Qt::Qml Qt::Gui Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::QCoro PkgConfig::GSTREAMER)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK)

View File

@@ -62,6 +62,26 @@ Controller::Controller(QObject *parent)
Connection::setRoomType<NeoChatRoom>();
Connection::setUserType<NeoChatUser>();
connect(this, &Controller::connectionAdded, this, [=, this](Connection *connection) {
connection->getTurnServers();
connect(connection, &Connection::turnServersChanged, this, [=, this](const QJsonObject &data) {
m_turnServers[connection->userId()].clear();
const auto username = QUrl::toPercentEncoding(data["username"].toString());
const auto password = QUrl::toPercentEncoding(data["password"].toString());
const auto ttl = data["ttl"].toInt();
for (const auto &uri : data["uris"].toArray()) {
const auto parts = uri.toString().split(":");
const auto port = parts[2].split("?")[0];
m_turnServers[connection->userId()] +=
parts[0] + QStringLiteral("://") + username + QStringLiteral(":") + password + QStringLiteral("@") + parts[1] + QStringLiteral(":") + port;
}
QTimer::singleShot(1000 * ttl, this, [this, connection]() {
connection->getTurnServers();
});
});
});
#ifndef Q_OS_ANDROID
TrayIcon *trayIcon = new TrayIcon(this);
if (NeoChatConfig::self()->systemTray()) {

View File

@@ -145,6 +145,7 @@ public Q_SLOTS:
private:
QNetworkConfigurationManager *m_mgr;
QHash<QString, QSet<QString>> m_turnServers; // userId -> List of TURN servers in the format that gstreamer needs
};
// TODO libQuotient 0.7: Drop

View File

@@ -66,8 +66,21 @@
#include "colorschemer.h"
#endif
#include "voip/audiosources.h"
#include "voip/devicemonitor.h"
#include "voip/pipelinemanager.h"
#include "voip/screencast.h"
#include "voip/videosources.h"
#include "voip/webrtcfoo.h"
#include "voip/windowmodel.h"
#include <gst/gst.h>
using namespace Quotient;
Q_DECLARE_METATYPE(GstDevice *)
Q_DECLARE_METATYPE(GstElement *)
#ifdef HAVE_KDBUSADDONS
static void raiseWindow(QWindow *window)
{
@@ -171,6 +184,25 @@ int main(int argc, char *argv[])
}
#endif
GError *error;
if (!gst_init_check(&argc, &argv, &error)) {
qWarning() << "Failed to initialize gstreamer";
return -1;
}
WebRTC();
g_object_unref(gst_element_factory_make("qmlglsink", nullptr));
DeviceMonitor::instance();
VideoSources::instance();
AudioSources::instance();
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "VideoSources", &VideoSources::instance());
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "AudioSources", &AudioSources::instance());
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "ScreenCastManager", &ScreenCastManager::instance());
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "PipelineManager", &PipelineManager::instance());
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "SinkModel", &SinkModel::instance());
qmlRegisterSingletonInstance("org.kde.voip", 1, 0, "WindowModel", &WindowModel::instance());
qRegisterMetaType<QVector<GstElement *>>();
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);

View File

@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
@@ -24,6 +25,10 @@
#include "csapi/rooms.h"
#include "csapi/typing.h"
#include "events/accountdataevents.h"
#include "events/callanswerevent.h"
#include "events/callcandidatesevent.h"
#include "events/callhangupevent.h"
#include "events/callinviteevent.h"
#include "events/reactionevent.h"
#include "events/roomcanonicalaliasevent.h"
#include "events/roommessageevent.h"
@@ -36,6 +41,8 @@
#include "user.h"
#include "utils.h"
#include "voip/callhandler.h"
#include <KLocalizedString>
NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinState)
@@ -58,6 +65,19 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
}
});
connect(this, &Room::displaynameChanged, this, &NeoChatRoom::displayNameChanged);
connect(this, &Room::callEvent, this, [=](Quotient::Room *, const Quotient::RoomEvent *event) {
// TODO throw away older events
if (const auto inviteEvent = eventCast<const CallInviteEvent>(event)) {
CallHandler::instance().handleInvite(inviteEvent);
} else if (const auto answerEvent = eventCast<const CallAnswerEvent>(event)) {
CallHandler::instance().handleAnswer(answerEvent);
} else if (const auto candidatesEvent = eventCast<const CallCandidatesEvent>(event)) {
CallHandler::instance().handleCandidates(candidatesEvent);
} else if (const auto hangupEvent = eventCast<const CallHangupEvent>(event)) {
CallHandler::instance().handleHangup(hangupEvent);
}
});
}
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)

48
src/voip/audiosources.cpp Normal file
View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "audiosources.h"
#include <gst/gst.h>
#include <QDebug>
#include <QString>
#include "devicemonitor.h"
int AudioSources::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return DeviceMonitor::instance().audioSources().size();
}
QVariant AudioSources::data(const QModelIndex &index, int role) const
{
if (index.row() >= DeviceMonitor::instance().audioSources().size()) {
return QVariant(QStringLiteral("DEADBEEF"));
}
if (role == TitleRole) {
return DeviceMonitor::instance().audioSources()[index.row()].title;
}
return QVariant();
}
QHash<int, QByteArray> AudioSources::roleNames() const
{
return {
{TitleRole, "title"},
};
}
AudioSources::AudioSources()
: QAbstractListModel()
{
connect(&DeviceMonitor::instance(), &DeviceMonitor::audioSourceAdded, this, [this]() {
beginResetModel();
endResetModel();
});
connect(&DeviceMonitor::instance(), &DeviceMonitor::audioSourceRemoved, this, [this]() {
beginResetModel();
endResetModel();
});
}

28
src/voip/audiosources.h Normal file
View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QAbstractListModel>
class AudioSources : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TitleRole = Qt::UserRole + 1,
};
static AudioSources &instance()
{
static AudioSources _instance;
return _instance;
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
AudioSources();
};

118
src/voip/devicemonitor.cpp Normal file
View File

@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "devicemonitor.h"
static gboolean deviceCallback(GstBus *bus, GstMessage *message, gpointer user_data)
{
Q_UNUSED(bus);
auto monitor = static_cast<DeviceMonitor *>(user_data);
return monitor->callback(message);
}
DeviceMonitor::DeviceMonitor()
: QObject()
, m_monitor(gst_device_monitor_new())
{
GstBus *bus;
GstCaps *caps;
bus = gst_device_monitor_get_bus(m_monitor);
gst_bus_add_watch(bus, deviceCallback, this);
gst_object_unref(bus);
if (!gst_device_monitor_start(m_monitor)) {
qWarning() << "Failed to start device monitor";
}
}
QVector<AudioSource> DeviceMonitor::audioSources() const
{
return m_audioSources;
}
QVector<VideoSource> DeviceMonitor::videoSources() const
{
return m_videoSources;
}
void DeviceMonitor::handleVideoSource(GstDevice *device)
{
VideoSource source;
source.title = QString(gst_device_get_display_name(device));
source.device = device;
auto caps = gst_device_get_caps(device);
auto size = gst_caps_get_size(caps);
for (int i = 0; i < size; i++) {
VideoCap videoCap;
GstStructure *cap = gst_caps_get_structure(caps, i);
gst_structure_get(cap, "width", G_TYPE_INT, &videoCap.width, "height", G_TYPE_INT, &videoCap.height, nullptr);
const auto framerate = gst_structure_get_value(cap, "framerate");
if (GST_VALUE_HOLDS_FRACTION(framerate)) {
auto numerator = gst_value_get_fraction_numerator(framerate);
auto denominator = gst_value_get_fraction_denominator(framerate);
videoCap.framerates += (float)numerator / denominator;
}
// unref cap?
source.caps += videoCap;
}
m_videoSources += source;
Q_EMIT videoSourceAdded();
}
void DeviceMonitor::handleAudioSource(GstDevice *device)
{
}
bool DeviceMonitor::callback(GstMessage *message)
{
GstDevice *device;
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_DEVICE_ADDED: {
gst_message_parse_device_added(message, &device);
auto name = gst_device_get_display_name(device);
auto deviceClass = QString(gst_device_get_device_class(device));
if (deviceClass == QStringLiteral("Video/Source")) {
handleVideoSource(device);
} else if (deviceClass == QStringLiteral("Audio/Source")) {
AudioSource _device;
_device.title = QString(name);
m_audioSources += _device;
Q_EMIT audioSourceAdded();
}
g_free(name);
gst_object_unref(device);
break;
}
case GST_MESSAGE_DEVICE_REMOVED: {
gst_message_parse_device_removed(message, &device);
auto name = gst_device_get_display_name(device);
auto deviceClass = QString(gst_device_get_device_class(device));
if (deviceClass == QStringLiteral("Video/Source")) {
m_videoSources.erase(std::remove_if(m_videoSources.begin(),
m_videoSources.end(),
[name](VideoSource d) {
return d.title == QString(name);
}),
m_videoSources.end());
Q_EMIT videoSourceRemoved();
} else if (deviceClass == QStringLiteral("Audio/Source")) {
m_audioSources.erase(std::remove_if(m_audioSources.begin(),
m_audioSources.end(),
[name](AudioSource d) {
return d.title == QString(name);
}),
m_audioSources.end());
Q_EMIT audioSourceRemoved();
}
g_free(name);
gst_object_unref(device);
break;
}
default:
break;
}
return G_SOURCE_CONTINUE;
}

56
src/voip/devicemonitor.h Normal file
View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QVector>
#include <gst/gst.h>
struct AudioSource {
QString title;
};
struct VideoCap {
int width;
int height;
QVector<float> framerates;
};
struct VideoSource {
QString title;
GstDevice *device;
QVector<VideoCap> caps;
};
class DeviceMonitor : public QObject
{
Q_OBJECT
public:
static DeviceMonitor &instance()
{
static DeviceMonitor _instance;
return _instance;
}
QVector<AudioSource> audioSources() const;
QVector<VideoSource> videoSources() const;
bool callback(GstMessage *message);
Q_SIGNALS:
void videoSourceAdded();
void audioSourceAdded();
void videoSourceRemoved();
void audioSourceRemoved();
private:
DeviceMonitor();
GstDeviceMonitor *m_monitor;
QVector<AudioSource> m_audioSources;
QVector<VideoSource> m_videoSources;
void handleVideoSource(GstDevice *device);
void handleAudioSource(GstDevice *device);
};

View File

@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "pipelinemanager.h"
#include <QtCore/QDebug>
#include <QtCore/QThread>
Q_DECLARE_METATYPE(GstElement *);
PipelineManager::PipelineManager()
{
}
GstElement *SinkModel::get(int index) const
{
return m_bins[index];
}
void PipelineManager::show(QQuickItem *item, int index)
{
auto pipeline = gst_pipeline_new(nullptr);
auto bin = SinkModel::instance().get(index);
if (gst_object_get_parent(GST_OBJECT(bin))) {
return;
}
GstElement *glUpload = gst_element_factory_make("glupload", nullptr);
GstElement *glcolorconvert = gst_element_factory_make("glcolorconvert", nullptr);
GstElement *glcolorbalance = gst_element_factory_make("glcolorbalance", nullptr);
GstElement *glImageSink = gst_element_factory_make("qmlglsink", nullptr);
g_object_set(glImageSink, "widget", item, nullptr);
gst_bin_add_many(GST_BIN(pipeline), bin, glUpload, glcolorconvert, glcolorbalance, glImageSink, nullptr);
gst_element_link_many(bin, glUpload, glcolorconvert, glcolorbalance, glImageSink, nullptr);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
}
void PipelineManager::add(GstElement *bin)
{
SinkModel::instance().add(bin);
}
void SinkModel::add(GstElement *bin)
{
beginInsertRows(QModelIndex(), m_bins.size(), m_bins.size());
m_bins += bin;
endInsertRows();
}
QHash<int, QByteArray> SinkModel::roleNames() const
{
return {{Element, "element"}};
}
int SinkModel::rowCount(const QModelIndex &parent) const
{
return m_bins.size();
}
QVariant SinkModel::data(const QModelIndex &index, int role) const
{
return QVariant::fromValue(m_bins[index.row()]);
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QAbstractListModel>
#include <QtCore/QObject>
#include <QtCore/QVector>
#include <QtQuick/QQuickItem>
#include <gst/gst.h>
class PipelineManager : public QObject
{
Q_OBJECT
public:
static PipelineManager &instance()
{
static PipelineManager _instance;
return _instance;
}
Q_INVOKABLE void show(QQuickItem *item, int index);
void add(GstElement *bin);
private:
PipelineManager();
};
class SinkModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
Element = Qt::UserRole + 1,
};
static SinkModel &instance()
{
static SinkModel _instance;
return _instance;
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void add(GstElement *bin);
GstElement *get(int index) const;
private:
QVector<GstElement *> m_bins;
};

37
src/voip/screencast.cpp Normal file
View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "screencast.h"
#include "pipelinemanager.h"
#include <QtCore/QByteArray>
#ifdef Q_OS_LINUX
#include "xscreencast.h"
#endif
ScreenCastManager::ScreenCastManager()
{
#ifdef Q_OS_LINUX
if (qgetenv("XDG_SESSION_TYPE") == QByteArrayLiteral("wayland")) {
qDebug() << "This is wayland";
// TODO wayland backend
} else if (qgetenv("XDG_SESSION_TYPE") == QByteArrayLiteral("x11")) {
qDebug() << "This is x11";
m_screencast = new XScreenCast(this);
} else {
qDebug() << "Unknown linux environment";
}
#endif
}
AbstractScreenCast::AbstractScreenCast(QObject *parent)
: QObject(parent)
{
}
void ScreenCastManager::requestScreenCast(int index)
{
if (m_screencast) {
PipelineManager::instance().add(m_screencast.value()->request(index));
}
}

44
src/voip/screencast.h Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtQuick/QQuickItem>
#include <gst/gst.h>
#include <optional>
/* Abstracts over screen sharing backends
* <= 1 will be available
* on some platforms (wayland), we can't select the window / screen
*/
class AbstractScreenCast : public QObject
{
Q_OBJECT
public:
virtual GstElement *request(int index) = 0;
virtual bool canSelectWindow() const = 0;
virtual bool canShareScreen() const = 0;
AbstractScreenCast(QObject *parent);
};
class ScreenCastManager : public QObject
{
Q_OBJECT
public:
static ScreenCastManager &instance()
{
static ScreenCastManager _instance;
return _instance;
}
Q_INVOKABLE void requestScreenCast(int index);
private:
std::optional<AbstractScreenCast *> m_screencast;
ScreenCastManager();
QQuickItem *m_item;
};

75
src/voip/videosources.cpp Normal file
View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "videosources.h"
#include <gst/gst.h>
#include "pipelinemanager.h"
#include <QDebug>
#include <QString>
#include "devicemonitor.h"
int VideoSources::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return DeviceMonitor::instance().videoSources().size();
}
QVariant VideoSources::data(const QModelIndex &index, int role) const
{
if (index.row() >= DeviceMonitor::instance().videoSources().size()) {
return QVariant(QStringLiteral("DEADBEEF"));
}
if (role == TitleRole) {
return DeviceMonitor::instance().videoSources()[index.row()].title;
}
return QVariant();
}
QHash<int, QByteArray> VideoSources::roleNames() const
{
return {
{TitleRole, "title"},
};
}
VideoSources::VideoSources()
: QAbstractListModel()
{
connect(&DeviceMonitor::instance(), &DeviceMonitor::videoSourceAdded, this, [this]() {
beginResetModel();
endResetModel();
});
connect(&DeviceMonitor::instance(), &DeviceMonitor::videoSourceRemoved, this, [this]() {
beginResetModel();
endResetModel();
});
}
void VideoSources::foo(int index)
{
auto device = DeviceMonitor::instance().videoSources()[index].device;
auto bin = gst_bin_new(nullptr);
GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
// GstElement *videorate = gst_element_factory_make("videorate", nullptr);
GstElement *filter = gst_element_factory_make("capsfilter", nullptr);
GstCaps *caps = gst_caps_new_simple("video/x-raw", "width", G_TYPE_INT, 1920, "height", G_TYPE_INT, 1080, "framerate", GST_TYPE_FRACTION, 5, 1, nullptr);
g_object_set(filter, "caps", caps, nullptr);
gst_caps_unref(caps);
GstElement *deviceElement = gst_device_create_element(device, nullptr);
gst_bin_add_many(GST_BIN(bin), deviceElement, videoconvert, filter, nullptr);
gst_element_link_many(deviceElement, videoconvert, filter, nullptr);
// GstPad *pad = gst_element_get_static_pad(filter, "src");
GstPad *pad = gst_element_get_static_pad(filter, "src");
auto ghostpad = gst_ghost_pad_new("src", pad);
gst_element_add_pad(bin, ghostpad);
gst_object_unref(pad);
PipelineManager::instance().add(bin);
}

33
src/voip/videosources.h Normal file
View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QAbstractListModel>
#include <gst/gst.h>
class VideoSources : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TitleRole = Qt::UserRole + 1,
DeviceRole,
};
static VideoSources &instance()
{
static VideoSources _instance;
return _instance;
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void foo(int index);
private:
VideoSources();
};

View File

@@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "waylandscreencast.h"

View File

@@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once

37
src/voip/windowmodel.cpp Normal file
View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "windowmodel.h"
#include <KWindowSystem>
int WindowModel::rowCount(const QModelIndex &parent) const
{
return KWindowSystem::windows().size();
}
QVariant WindowModel::data(const QModelIndex &index, int role) const
{
if (role == TitleRole) {
return KWindowInfo(KWindowSystem::windows()[index.row()], NET::WMName).name();
} else if (role == IdRole) {
return KWindowSystem::windows()[index.row()];
}
return QStringLiteral("FOO");
}
QHash<int, QByteArray> WindowModel::roleNames() const
{
return {{TitleRole, "title"}, {IdRole, "id"}};
}
WindowModel::WindowModel()
{
connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, [=]() {
beginResetModel();
endResetModel();
});
connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, [=]() {
beginResetModel();
endResetModel();
});
}

30
src/voip/windowmodel.h Normal file
View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QtCore/QAbstractListModel>
class WindowModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TitleRole = Qt::UserRole + 1,
IdRole,
};
static WindowModel &instance()
{
static WindowModel _instance;
return _instance;
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
WindowModel();
};

44
src/voip/xscreencast.cpp Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "xscreencast.h"
GstElement *XScreenCast::request(int index)
{
GstElement *bin = gst_bin_new(nullptr);
qDebug() << "index" << index;
GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "ximagesrc");
g_object_set(ximagesrc, "xid", index, nullptr);
g_object_set(ximagesrc, "use-damage", true, nullptr);
// GstElement *filter = gst_element_factory_make("capsfilter", "capsfilter");
// GstCaps *caps = gst_caps_new_simple("video/x-raw", "width", G_TYPE_INT, 1920, "height", G_TYPE_INT, 1080, "framerate", GST_TYPE_FRACTION, 30, 1,
// nullptr); g_object_set(filter, "caps", caps, nullptr); gst_caps_unref(caps);
GstElement *queue = gst_element_factory_make("queue", "queue");
gst_bin_add_many(GST_BIN(bin), ximagesrc, queue, /*filter,*/ nullptr);
gst_element_link_many(ximagesrc, queue /*, filter*/, nullptr);
// GstPad *pad = gst_element_get_static_pad(filter, "src");
GstPad *pad = gst_element_get_static_pad(queue, "src");
auto ghostpad = gst_ghost_pad_new("src", pad);
gst_element_add_pad(bin, ghostpad);
gst_object_unref(pad);
return bin;
}
XScreenCast::XScreenCast(QObject *parent)
: AbstractScreenCast(parent)
{
}
bool XScreenCast::canSelectWindow() const
{
return true;
}
bool XScreenCast::canShareScreen() const
{
return true;
}

15
src/voip/xscreencast.h Normal file
View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "screencast.h"
class XScreenCast : public AbstractScreenCast
{
public:
GstElement *request(int index) override;
XScreenCast(QObject *parent);
bool canShareScreen() const override;
bool canSelectWindow() const override;
};