diff --git a/CMakeLists.txt b/CMakeLists.txt index 71711f903..b6513c44e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt old mode 100755 new mode 100644 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5f1b34ec..245797d2e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/controller.cpp b/src/controller.cpp index 2def31b7f..cb291d8c6 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -62,6 +62,26 @@ Controller::Controller(QObject *parent) Connection::setRoomType(); Connection::setUserType(); + 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()) { diff --git a/src/controller.h b/src/controller.h index 67077ad80..ca5e7d50b 100644 --- a/src/controller.h +++ b/src/controller.h @@ -145,6 +145,7 @@ public Q_SLOTS: private: QNetworkConfigurationManager *m_mgr; + QHash> m_turnServers; // userId -> List of TURN servers in the format that gstreamer needs }; // TODO libQuotient 0.7: Drop diff --git a/src/main.cpp b/src/main.cpp index f42b5ed33..1418eadb6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 + 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>(); + 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); diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index dadfa1024..a1e826354 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -1,3 +1,4 @@ + // SPDX-FileCopyrightText: 2018-2020 Black Hat // 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 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(event)) { + CallHandler::instance().handleInvite(inviteEvent); + } else if (const auto answerEvent = eventCast(event)) { + CallHandler::instance().handleAnswer(answerEvent); + } else if (const auto candidatesEvent = eventCast(event)) { + CallHandler::instance().handleCandidates(candidatesEvent); + } else if (const auto hangupEvent = eventCast(event)) { + CallHandler::instance().handleHangup(hangupEvent); + } + }); } void NeoChatRoom::uploadFile(const QUrl &url, const QString &body) diff --git a/src/voip/audiosources.cpp b/src/voip/audiosources.cpp new file mode 100644 index 000000000..1044e7abc --- /dev/null +++ b/src/voip/audiosources.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "audiosources.h" + +#include + +#include +#include + +#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 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(); + }); +} diff --git a/src/voip/audiosources.h b/src/voip/audiosources.h new file mode 100644 index 000000000..f0e8e3f11 --- /dev/null +++ b/src/voip/audiosources.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include + +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 roleNames() const override; + +private: + AudioSources(); +}; diff --git a/src/voip/devicemonitor.cpp b/src/voip/devicemonitor.cpp new file mode 100644 index 000000000..a2028fd85 --- /dev/null +++ b/src/voip/devicemonitor.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// 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(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 DeviceMonitor::audioSources() const +{ + return m_audioSources; +} + +QVector 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; +} diff --git a/src/voip/devicemonitor.h b/src/voip/devicemonitor.h new file mode 100644 index 000000000..0d432b4c4 --- /dev/null +++ b/src/voip/devicemonitor.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +struct AudioSource { + QString title; +}; +struct VideoCap { + int width; + int height; + QVector framerates; +}; + +struct VideoSource { + QString title; + GstDevice *device; + QVector caps; +}; + +class DeviceMonitor : public QObject +{ + Q_OBJECT + +public: + static DeviceMonitor &instance() + { + static DeviceMonitor _instance; + return _instance; + } + + QVector audioSources() const; + QVector videoSources() const; + bool callback(GstMessage *message); + +Q_SIGNALS: + void videoSourceAdded(); + void audioSourceAdded(); + + void videoSourceRemoved(); + void audioSourceRemoved(); + +private: + DeviceMonitor(); + GstDeviceMonitor *m_monitor; + QVector m_audioSources; + QVector m_videoSources; + void handleVideoSource(GstDevice *device); + void handleAudioSource(GstDevice *device); +}; diff --git a/src/voip/pipelinemanager.cpp b/src/voip/pipelinemanager.cpp new file mode 100644 index 000000000..75aaff0ce --- /dev/null +++ b/src/voip/pipelinemanager.cpp @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "pipelinemanager.h" +#include +#include + +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 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()]); +} diff --git a/src/voip/pipelinemanager.h b/src/voip/pipelinemanager.h new file mode 100644 index 000000000..08937012c --- /dev/null +++ b/src/voip/pipelinemanager.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +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 roleNames() const override; + void add(GstElement *bin); + GstElement *get(int index) const; + +private: + QVector m_bins; +}; diff --git a/src/voip/screencast.cpp b/src/voip/screencast.cpp new file mode 100644 index 000000000..80e55a45d --- /dev/null +++ b/src/voip/screencast.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "screencast.h" +#include "pipelinemanager.h" +#include + +#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)); + } +} diff --git a/src/voip/screencast.h b/src/voip/screencast.h new file mode 100644 index 000000000..58a40bb34 --- /dev/null +++ b/src/voip/screencast.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include + +/* 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 m_screencast; + ScreenCastManager(); + QQuickItem *m_item; +}; diff --git a/src/voip/videosources.cpp b/src/voip/videosources.cpp new file mode 100644 index 000000000..c9371554f --- /dev/null +++ b/src/voip/videosources.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "videosources.h" + +#include + +#include "pipelinemanager.h" +#include +#include + +#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 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); +} diff --git a/src/voip/videosources.h b/src/voip/videosources.h new file mode 100644 index 000000000..63ed23a31 --- /dev/null +++ b/src/voip/videosources.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include + +#include + +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 roleNames() const override; + + Q_INVOKABLE void foo(int index); + +private: + VideoSources(); +}; diff --git a/src/voip/waylandscreencast.cpp b/src/voip/waylandscreencast.cpp new file mode 100644 index 000000000..fee55a051 --- /dev/null +++ b/src/voip/waylandscreencast.cpp @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "waylandscreencast.h" diff --git a/src/voip/waylandscreencast.h b/src/voip/waylandscreencast.h new file mode 100644 index 000000000..d8b60213f --- /dev/null +++ b/src/voip/waylandscreencast.h @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once diff --git a/src/voip/windowmodel.cpp b/src/voip/windowmodel.cpp new file mode 100644 index 000000000..44e107fac --- /dev/null +++ b/src/voip/windowmodel.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "windowmodel.h" + +#include + +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 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(); + }); +} diff --git a/src/voip/windowmodel.h b/src/voip/windowmodel.h new file mode 100644 index 000000000..110287a3a --- /dev/null +++ b/src/voip/windowmodel.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include + +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 roleNames() const override; + +private: + WindowModel(); +}; diff --git a/src/voip/xscreencast.cpp b/src/voip/xscreencast.cpp new file mode 100644 index 000000000..51600a358 --- /dev/null +++ b/src/voip/xscreencast.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// 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; +} diff --git a/src/voip/xscreencast.h b/src/voip/xscreencast.h new file mode 100644 index 000000000..6881949da --- /dev/null +++ b/src/voip/xscreencast.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// 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; +};