From 1ba6a4d78d27181847bb1fd45838a70ae0c853e5 Mon Sep 17 00:00:00 2001 From: David Elsing Date: Sat, 11 Mar 2023 14:36:51 +0100 Subject: Support screen sharing with xdg-desktop-portal --- src/voip/CallManager.cpp | 229 ++++++++++++++++---- src/voip/CallManager.h | 31 ++- src/voip/ScreenCastPortal.cpp | 471 ++++++++++++++++++++++++++++++++++++++++++ src/voip/ScreenCastPortal.h | 66 ++++++ src/voip/WebRTCSession.cpp | 128 +++++++++--- src/voip/WebRTCSession.h | 40 ++-- 6 files changed, 877 insertions(+), 88 deletions(-) create mode 100644 src/voip/ScreenCastPortal.cpp create mode 100644 src/voip/ScreenCastPortal.h (limited to 'src') diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp index 02fc3100..4471a0c2 100644 --- a/src/voip/CallManager.cpp +++ b/src/voip/CallManager.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -22,6 +23,8 @@ #include "Utils.h" #include "mtx/responses/turn_server.hpp" +#include "voip/ScreenCastPortal.h" +#include "voip/WebRTCSession.h" /* * Select Answer when one instance of the client supports v0 @@ -47,6 +50,7 @@ using namespace mtx::events; using namespace mtx::events::voip; using webrtc::CallType; +using webrtc::ScreenShareType; //! Session Description Object typedef RTCSessionDescriptionInit SDO; @@ -64,6 +68,12 @@ CallManager::CallManager(QObject *parent) qRegisterMetaType(); qRegisterMetaType(); + if (screenShareX11Available()) { + screenShareType_ = ScreenShareType::X11; + } else { + screenShareType_ = ScreenShareType::XDP; + } + connect( &session_, &WebRTCSession::offerCreated, @@ -176,6 +186,13 @@ CallManager::CallManager(QObject *parent) break; } }); + +#ifdef GSTREAMER_AVAILABLE + connect(&ScreenCastPortal::instance(), + &ScreenCastPortal::readyChanged, + this, + &CallManager::screenShareChanged); +#endif } void @@ -191,8 +208,10 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); std::string errorMessage; - if (!session_.havePlugins( - callType != CallType::VOICE, callType == CallType::SCREEN, &errorMessage)) { + if (!session_.havePlugins(callType != CallType::VOICE, + callType == CallType::SCREEN, + screenShareType_, + &errorMessage)) { emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); return; } @@ -212,14 +231,22 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w return; } +#ifdef GSTREAMER_AVAILABLE if (callType == CallType::SCREEN) { - if (!screenShareSupported()) - return; - if (windows_.empty() || windowIndex >= windows_.size()) { - nhlog::ui()->error("WebRTC: window index out of range"); - return; + if (screenShareType_ == ScreenShareType::X11) { + if (windows_.empty() || windowIndex >= windows_.size()) { + nhlog::ui()->error("WebRTC: window index out of range"); + return; + } + } else { + ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); + if (sc_portal.getStream() == nullptr) { + nhlog::ui()->error("xdg-desktop-portal stream not started"); + return; + } } } +#endif if (haveCallInvite_) { nhlog::ui()->debug( @@ -255,8 +282,12 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w invitee_ = callParty_.toStdString(); emit newInviteState(); playRingtone(QUrl(QStringLiteral("qrc:/media/media/ringback.ogg")), true); - if (!session_.createOffer(callType, - callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) { + + uint32_t shareWindowId = + callType == CallType::SCREEN && screenShareType_ == ScreenShareType::X11 + ? windows_[windowIndex].second + : 0; + if (!session_.createOffer(callType, screenShareType_, shareWindowId)) { emit ChatPage::instance()->showNotification(QStringLiteral("Problem setting up call.")); endCall(); } @@ -466,8 +497,10 @@ CallManager::acceptInvite() stopRingtone(); std::string errorMessage; - if (!session_.havePlugins( - callType_ != CallType::VOICE, callType_ == CallType::SCREEN, &errorMessage)) { + if (!session_.havePlugins(callType_ != CallType::VOICE, + callType_ == CallType::SCREEN, + screenShareType_, + &errorMessage)) { emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); hangUp(CallHangUp::Reason::UserMediaFailed); return; @@ -713,9 +746,13 @@ CallManager::callsSupported() } bool -CallManager::screenShareSupported() +CallManager::screenShareX11Available() { - return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY"); +#ifdef GSTREAMER_AVAILABLE + return std::getenv("DISPLAY"); +#else + return false; +#endif } QStringList @@ -746,6 +783,7 @@ CallManager::generateCallID() void CallManager::clear(bool endAllCalls) { + closeScreenShare(); roomid_.clear(); callParty_.clear(); callPartyDisplayName_.clear(); @@ -810,9 +848,27 @@ CallManager::stopRingtone() player_.setPlaylist(nullptr); } +bool +CallManager::screenShareReady() const +{ +#ifdef GSTREAMER_AVAILABLE + if (screenShareType_ == ScreenShareType::X11) { + return true; + } else { + return ScreenCastPortal::instance().ready(); + } +#else + return false; +#endif +} + QStringList CallManager::windowList() { + if (!screenShareX11Available()) { + return {}; + } + windows_.clear(); windows_.push_back({tr("Entire screen"), 0}); @@ -880,26 +936,52 @@ namespace { GstElement *pipe_ = nullptr; unsigned int busWatchId_ = 0; +void +close_preview_stream() +{ + if (pipe_) { + gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL); + gst_object_unref(pipe_); + pipe_ = nullptr; + } + if (busWatchId_) { + g_source_remove(busWatchId_); + busWatchId_ = 0; + } +} + gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer G_GNUC_UNUSED) { switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: - if (pipe_) { - gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL); - gst_object_unref(pipe_); - pipe_ = nullptr; - } - if (busWatchId_) { - g_source_remove(busWatchId_); - busWatchId_ = 0; - } + close_preview_stream(); + break; + case GST_MESSAGE_ERROR: { + GError *err = nullptr; + gchar *dbg_info = nullptr; + gst_message_parse_error(msg, &err, &dbg_info); + nhlog::ui()->error("GST error: {}", dbg_info); + g_error_free(err); + g_free(dbg_info); + close_preview_stream(); break; + } default: break; } return TRUE; } + +static GstElement * +make_preview_sink() +{ + if (QGuiApplication::platformName() == QStringLiteral("wayland")) { + return gst_element_factory_make("waylandsink", nullptr); + } else { + return gst_element_factory_make("ximagesink", nullptr); + } +} } #endif @@ -907,38 +989,81 @@ void CallManager::previewWindow(unsigned int index) const { #ifdef GSTREAMER_AVAILABLE - if (windows_.empty() || index >= windows_.size() || !gst_is_initialized()) + if (!gst_is_initialized()) + return; + + if (pipe_ != nullptr) { + nhlog::ui()->warn("Preview already started"); return; + } - GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr); - if (!ximagesrc) { - nhlog::ui()->error("Failed to create ximagesrc"); + if (screenShareType_ == ScreenShareType::X11 && + (!screenShareX11Available() || windows_.empty() || index >= windows_.size())) { + nhlog::ui()->error("X11 screencast not available"); return; } + + auto settings = ChatPage::instance()->userSettings(); + + pipe_ = gst_pipeline_new(nullptr); + GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr); GstElement *videoscale = gst_element_factory_make("videoscale", nullptr); GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr); - GstElement *ximagesink = gst_element_factory_make("ximagesink", nullptr); + GstElement *preview_sink = make_preview_sink(); + GstElement *videorate = gst_element_factory_make("videorate", nullptr); - g_object_set(ximagesrc, "use-damage", FALSE, nullptr); - g_object_set(ximagesrc, "show-pointer", FALSE, nullptr); - g_object_set(ximagesrc, "xid", windows_[index].second, nullptr); + gst_bin_add_many( + GST_BIN(pipe_), videorate, videoconvert, videoscale, capsfilter, preview_sink, nullptr); GstCaps *caps = gst_caps_new_simple( - "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, nullptr); + "video/x-raw", "framerate", GST_TYPE_FRACTION, settings->screenShareFrameRate(), 1, nullptr); g_object_set(capsfilter, "caps", caps, nullptr); gst_caps_unref(caps); - pipe_ = gst_pipeline_new(nullptr); - gst_bin_add_many( - GST_BIN(pipe_), ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr); + GstElement *screencastsrc = nullptr; + if (screenShareType_ == ScreenShareType::X11) { + GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr); + if (!ximagesrc) { + nhlog::ui()->error("Failed to create ximagesrc"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return; + } + g_object_set(ximagesrc, "use-damage", FALSE, nullptr); + g_object_set(ximagesrc, "xid", windows_[index].second, nullptr); + g_object_set(ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr); + g_object_set(ximagesrc, "do-timestamp", (gboolean)1, nullptr); + + gst_bin_add(GST_BIN(pipe_), ximagesrc); + screencastsrc = ximagesrc; + } else { + ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); + const ScreenCastPortal::Stream *stream = sc_portal.getStream(); + if (stream == nullptr) { + nhlog::ui()->error("xdg-desktop-portal stream not started"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return; + } + GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", nullptr); + g_object_set(pipewiresrc, "fd", (gint)stream->fd, nullptr); + std::string path = std::to_string(stream->nodeId); + g_object_set(pipewiresrc, "path", path.c_str(), nullptr); + g_object_set(pipewiresrc, "do-timestamp", (gboolean)1, nullptr); + + gst_bin_add(GST_BIN(pipe_), pipewiresrc); + screencastsrc = pipewiresrc; + } + if (!gst_element_link_many( - ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr)) { + screencastsrc, videorate, videoconvert, videoscale, capsfilter, preview_sink, nullptr)) { nhlog::ui()->error("Failed to link preview window elements"); gst_object_unref(pipe_); pipe_ = nullptr; return; } + if (gst_element_set_state(pipe_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { nhlog::ui()->error("Unable to start preview pipeline"); gst_object_unref(pipe_); @@ -954,6 +1079,40 @@ CallManager::previewWindow(unsigned int index) const #endif } +void +CallManager::setupScreenShareXDP() +{ +#ifdef GSTREAMER_AVAILABLE + ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); + sc_portal.init(); + screenShareType_ = ScreenShareType::XDP; +#endif +} + +void +CallManager::setScreenShareType(webrtc::ScreenShareType screenShareType) +{ +#ifdef GSTREAMER_AVAILABLE + closeScreenShare(); + screenShareType_ = screenShareType; + emit screenShareChanged(); +#else + (void)screenShareType; +#endif +} + +void +CallManager::closeScreenShare() +{ +#ifdef GSTREAMER_AVAILABLE + close_preview_stream(); + if (!isOnCall()) { + ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); + sc_portal.close(); + } +#endif +} + namespace { std::vector getTurnURIs(const mtx::responses::TurnServer &turnServer) diff --git a/src/voip/CallManager.h b/src/voip/CallManager.h index 8c15d5ba..5d7ebda9 100644 --- a/src/voip/CallManager.h +++ b/src/voip/CallManager.h @@ -17,6 +17,7 @@ #include "WebRTCSession.h" #include "mtx/events/collections.hpp" #include "mtx/events/voip.hpp" +#include "voip/ScreenCastPortal.h" #include namespace mtx::responses { @@ -32,6 +33,8 @@ class CallManager final : public QObject Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) Q_PROPERTY(bool isOnCallOnOtherDevice READ isOnCallOnOtherDevice NOTIFY newCallDeviceState) Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState) + Q_PROPERTY( + webrtc::ScreenShareType screenShareType READ screenShareType NOTIFY screenShareChanged) Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState) @@ -41,7 +44,8 @@ class CallManager final : public QObject Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged) Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged) Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) - Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT) + Q_PROPERTY(bool screenShareX11Available READ screenShareX11Available CONSTANT) + Q_PROPERTY(bool screenShareReady READ screenShareReady NOTIFY screenShareChanged) public: CallManager(QObject *); @@ -51,6 +55,7 @@ public: bool isOnCallOnOtherDevice() const { return (isOnCallOnOtherDevice_ != ""); } bool checkSharesRoom(QString roomid_, std::string invitee) const; webrtc::CallType callType() const { return callType_; } + webrtc::ScreenShareType screenShareType() const { return screenShareType_; } webrtc::State callState() const { return session_.state(); } QString callParty() const { return callParty_; } QString callPartyDisplayName() const { return callPartyDisplayName_; } @@ -60,9 +65,10 @@ public: QStringList mics() const { return devices(false); } QStringList cameras() const { return devices(true); } void refreshTurnServer(); + bool screenShareReady() const; static bool callsSupported(); - static bool screenShareSupported(); + static bool screenShareX11Available(); public slots: void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0); @@ -73,6 +79,9 @@ public slots: void hangUp( mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::UserHangUp); void rejectInvite(); + void setupScreenShareXDP(); + void setScreenShareType(webrtc::ScreenShareType); + void closeScreenShare(); QStringList windowList(); void previewWindow(unsigned int windowIndex) const; @@ -90,6 +99,7 @@ signals: void micMuteChanged(); void devicesChanged(); void turnServerRetrieved(const mtx::responses::TurnServer &); + void screenShareChanged(); private slots: void retrieveTurnServer(); @@ -102,14 +112,15 @@ private: QString callPartyAvatarUrl_; std::string callPartyVersion_ = "1"; std::string callid_; - std::string partyid_ = mtx::client::utils::random_token(8, false); - std::string selectedpartyid_ = ""; - std::string invitee_ = ""; - const uint32_t timeoutms_ = 120000; - webrtc::CallType callType_ = webrtc::CallType::VOICE; - bool haveCallInvite_ = false; - bool answerSelected_ = false; - std::string isOnCallOnOtherDevice_ = ""; + std::string partyid_ = mtx::client::utils::random_token(8, false); + std::string selectedpartyid_ = ""; + std::string invitee_ = ""; + const uint32_t timeoutms_ = 120000; + webrtc::CallType callType_ = webrtc::CallType::VOICE; + webrtc::ScreenShareType screenShareType_ = webrtc::ScreenShareType::X11; + bool haveCallInvite_ = false; + bool answerSelected_ = false; + std::string isOnCallOnOtherDevice_ = ""; std::string inviteSDP_; std::vector remoteICECandidates_; std::vector turnURIs_; diff --git a/src/voip/ScreenCastPortal.cpp b/src/voip/ScreenCastPortal.cpp new file mode 100644 index 00000000..321373d9 --- /dev/null +++ b/src/voip/ScreenCastPortal.cpp @@ -0,0 +1,471 @@ +#ifdef GSTREAMER_AVAILABLE + +#include "ScreenCastPortal.h" +#include "ChatPage.h" +#include "Logging.h" +#include "UserSettingsPage.h" + +#include +#include +#include +#include +#include +#include + +static QString +make_token() +{ + thread_local std::random_device rng; + std::uniform_int_distribution index_dist(0, 9); + + std::string token; + token.reserve(5 + 64); + token += "nheko"; + + for (uint8_t i = 0; i < 64; ++i) + token.push_back('0' + index_dist(rng)); + + return QString::fromStdString(std::move(token)); +} + +static QString +handle_path(QString handle_token) +{ + QString sender = QDBusConnection::sessionBus().baseService(); + if (sender[0] == ':') + sender.remove(0, 1); + sender.replace(".", "_"); + return QStringLiteral("/org/freedesktop/portal/desktop/request/") + sender + + QStringLiteral("/") + handle_token; +} + +void +ScreenCastPortal::init() +{ + switch (state) { + case State::Closed: + state = State::Starting; + createSession(); + break; + case State::Starting: + nhlog::ui()->warn("ScreenCastPortal already starting"); + break; + case State::Started: + close(true); + break; + case State::Closing: + nhlog::ui()->warn("ScreenCastPortal still closing"); + break; + } +} + +const ScreenCastPortal::Stream * +ScreenCastPortal::getStream() const +{ + if (state != State::Started) + return nullptr; + else + return &stream; +} + +bool +ScreenCastPortal::ready() const +{ + return state == State::Started; +} + +void +ScreenCastPortal::close(bool reinit) +{ + switch (state) { + case State::Closed: + if (reinit) + init(); + break; + case State::Starting: + if (!reinit) { + // Remaining handler will abort. + state = State::Closed; + } + break; + case State::Started: { + state = State::Closing; + emit readyChanged(); + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + sessionHandle.path(), + QStringLiteral("org.freedesktop.portal.Session"), + QStringLiteral("Close")); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, + &QDBusPendingCallWatcher::finished, + this, + [this, reinit](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + if (!reply.isValid()) { + nhlog::ui()->warn("org.freedesktop.portal.ScreenCast (Close): {}", + reply.error().message().toStdString()); + } + state = State::Closed; + if (reinit) + init(); + }); + } break; + case State::Closing: + nhlog::ui()->warn("ScreenCastPortal already closing"); + break; + } +} + +void +ScreenCastPortal::closedHandler(uint response, const QVariantMap &) +{ + if (response != 0) { + nhlog::ui()->error("org.freedekstop.portal.ScreenCast (Closed): {}", response); + } + + nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: Connection closed"); + state = State::Closed; + emit readyChanged(); +} + +void +ScreenCastPortal::createSession() +{ + // Connect before sending the request to avoid missing the reply + QString handle_token = make_token(); + QDBusConnection::sessionBus().connect(QStringLiteral("org.freedesktop.portal.Desktop"), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + this, + SLOT(createSessionHandler(uint, QVariantMap))); + + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.ScreenCast"), + QStringLiteral("CreateSession")); + msg << QVariantMap{{QStringLiteral("handle_token"), handle_token}, + {QStringLiteral("session_handle_token"), make_token()}}; + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (CreateSession): {}", + reply.error().message().toStdString()); + close(); + } + }); +} + +void +ScreenCastPortal::createSessionHandler(uint response, const QVariantMap &results) +{ + switch (state) { + case State::Closed: + nhlog::ui()->warn("ScreenCastPortal not starting"); + break; + case State::Starting: { + if (response != 0) { + nhlog::ui()->error("org.freedekstop.portal.ScreenCast (CreateSession Response): {}", + response); + close(); + return; + } + + sessionHandle = QDBusObjectPath(results.value(QStringLiteral("session_handle")).toString()); + + nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: sessionHandle = {}", + sessionHandle.path().toStdString()); + + getAvailableSourceTypes(); + } break; + case State::Started: + nhlog::ui()->warn("ScreenCastPortal already started"); + break; + case State::Closing: + break; + } +} + +void +ScreenCastPortal::getAvailableSourceTypes() +{ + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("Get")); + msg << QStringLiteral("org.freedesktop.portal.ScreenCast") + << QStringLiteral("AvailableSourceTypes"); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.DBus.Properties (Get AvailableSourceTypes): {}", + reply.error().message().toStdString()); + close(); + return; + } + + switch (state) { + case State::Closed: + nhlog::ui()->warn("ScreenCastPortal not starting"); + break; + case State::Starting: { + const auto &value = reply.value().variant(); + if (value.canConvert()) { + availableSourceTypes = value.value(); + } else { + nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " + "AvailableSourceTypes)"); + close(); + return; + } + + getAvailableCursorModes(); + } break; + case State::Started: + nhlog::ui()->warn("ScreenCastPortal already started"); + break; + case State::Closing: + break; + } + }); +} + +void +ScreenCastPortal::getAvailableCursorModes() +{ + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("Get")); + msg << QStringLiteral("org.freedesktop.portal.ScreenCast") + << QStringLiteral("AvailableCursorModes"); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.DBus.Properties (Get AvailableCursorModes): {}", + reply.error().message().toStdString()); + close(); + return; + } + + switch (state) { + case State::Closed: + nhlog::ui()->warn("ScreenCastPortal not starting"); + break; + case State::Starting: { + const auto &value = reply.value().variant(); + if (value.canConvert()) { + availableCursorModes = value.value(); + } else { + nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " + "AvailableCursorModes)"); + close(); + return; + } + + selectSources(); + } break; + case State::Started: + nhlog::ui()->warn("ScreenCastPortal already started"); + break; + case State::Closing: + break; + } + }); +} + +void +ScreenCastPortal::selectSources() +{ + // Connect before sending the request to avoid missing the reply + auto handle_token = make_token(); + QDBusConnection::sessionBus().connect(QString(), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + this, + SLOT(selectSourcesHandler(uint, QVariantMap))); + + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.ScreenCast"), + QStringLiteral("SelectSources")); + + QVariantMap options{{QStringLiteral("multiple"), false}, + {QStringLiteral("types"), availableSourceTypes}, + {QStringLiteral("handle_token"), handle_token}}; + + auto settings = ChatPage::instance()->userSettings(); + if (settings->screenShareHideCursor() && (availableCursorModes & (uint)1) != 0) { + options["cursor_mode"] = (uint)1; + } + + msg << QVariant::fromValue(sessionHandle) << options; + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (SelectSources): {}", + reply.error().message().toStdString()); + close(); + } + }); +} + +void +ScreenCastPortal::selectSourcesHandler(uint response, const QVariantMap &) +{ + switch (state) { + case State::Closed: + nhlog::ui()->warn("ScreenCastPortal not starting"); + break; + case State::Starting: { + if (response != 0) { + nhlog::ui()->error("org.freedekstop.portal.ScreenCast (SelectSources Response): {}", + response); + close(); + return; + } + start(); + } break; + case State::Started: + nhlog::ui()->warn("ScreenCastPortal already started"); + break; + case State::Closing: + break; + } +} + +void +ScreenCastPortal::start() +{ + // Connect before sending the request to avoid missing the reply + auto handle_token = make_token(); + QDBusConnection::sessionBus().connect(QString(), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + this, + SLOT(startHandler(uint, QVariantMap))); + + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.ScreenCast"), + QStringLiteral("Start")); + msg << QVariant::fromValue(sessionHandle) << QString() + << QVariantMap{{QStringLiteral("handle_token"), handle_token}}; + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (Start): {}", + reply.error().message().toStdString()); + } else { + } + }); +} + +struct PipeWireStream +{ + quint32 nodeId = 0; + QVariantMap map; +}; + +Q_DECLARE_METATYPE(PipeWireStream) + +const QDBusArgument & +operator>>(const QDBusArgument &argument, PipeWireStream &stream) +{ + argument.beginStructure(); + argument >> stream.nodeId; + argument.beginMap(); + while (!argument.atEnd()) { + QString key; + QVariant map; + argument.beginMapEntry(); + argument >> key >> map; + argument.endMapEntry(); + stream.map.insert(key, map); + } + argument.endMap(); + argument.endStructure(); + return argument; +} + +void +ScreenCastPortal::startHandler(uint response, const QVariantMap &results) +{ + if (response != 0) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (Start Response): {}", response); + close(); + return; + } + + QVector streams = + qdbus_cast>(results.value(QStringLiteral("streams"))); + if (streams.size() == 0) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast: No stream was returned"); + close(); + return; + } + + stream.nodeId = streams[0].nodeId; + nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: nodeId = {}", stream.nodeId); + openPipeWireRemote(); +} + +void +ScreenCastPortal::openPipeWireRemote() +{ + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), + QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.ScreenCast"), + QStringLiteral("OpenPipeWireRemote")); + msg << QVariant::fromValue(sessionHandle) << QVariantMap{}; + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect( + watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + if (!reply.isValid()) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (OpenPipeWireRemote): {}", + reply.error().message().toStdString()); + close(); + } else { + stream.fd = reply.value().fileDescriptor(); + nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: fd = {}", stream.fd); + + state = State::Started; + emit readyChanged(); + } + }); +} + +#endif diff --git a/src/voip/ScreenCastPortal.h b/src/voip/ScreenCastPortal.h new file mode 100644 index 00000000..068dbddb --- /dev/null +++ b/src/voip/ScreenCastPortal.h @@ -0,0 +1,66 @@ +#pragma once + +#ifdef GSTREAMER_AVAILABLE + +#include +#include +#include +#include +#include +#include + +class ScreenCastPortal final : public QObject +{ + Q_OBJECT + +public: + struct Stream + { + int fd; + quint32 nodeId; + }; + + static ScreenCastPortal &instance() + { + static ScreenCastPortal instance; + return instance; + } + + void init(); + const Stream *getStream() const; + bool ready() const; + void close(bool reinit = false); + +public slots: + void createSessionHandler(uint response, const QVariantMap &results); + void closedHandler(uint response, const QVariantMap &results); + void selectSourcesHandler(uint response, const QVariantMap &results); + void startHandler(uint response, const QVariantMap &results); + +signals: + void readyChanged(); + +private: + void createSession(); + void getAvailableSourceTypes(); + void getAvailableCursorModes(); + void selectSources(); + void start(); + void openPipeWireRemote(); + QDBusObjectPath sessionHandle; + uint availableSourceTypes; + uint availableCursorModes; + + Stream stream; + + enum class State + { + Closed, + Starting, + Started, + Closing, + }; + State state = State::Closed; +}; + +#endif diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index afced81a..ba75c744 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include @@ -19,6 +20,7 @@ #include "Logging.h" #include "UserSettingsPage.h" #include "WebRTCSession.h" +#include "voip/ScreenCastPortal.h" #ifdef GSTREAMER_AVAILABLE extern "C" @@ -40,9 +42,11 @@ extern "C" #define STUN_SERVER "stun://turn.matrix.org:3478" Q_DECLARE_METATYPE(webrtc::CallType) +Q_DECLARE_METATYPE(webrtc::ScreenShareType) Q_DECLARE_METATYPE(webrtc::State) using webrtc::CallType; +using webrtc::ScreenShareType; using webrtc::State; WebRTCSession::WebRTCSession() @@ -56,6 +60,14 @@ WebRTCSession::WebRTCSession() "CallType", QStringLiteral("Can't instantiate enum")); + qRegisterMetaType(); + qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, + "im.nheko", + 1, + 0, + "ScreenShareType", + QStringLiteral("Can't instantiate enum")); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, "im.nheko", @@ -578,13 +590,13 @@ getMediaAttributes(const GstSDPMessage *sdp, } bool -WebRTCSession::havePlugins(bool isVideo, bool isX11Screenshare, std::string *errorMessage) +WebRTCSession::havePlugins(bool isVideo, + bool isScreenshare, + ScreenShareType screenShareType, + std::string *errorMessage) { if (!initialised_ && !init(errorMessage)) return false; - if (haveVoicePlugins_ && (!isVideo || haveVideoPlugins_) && - (!isX11Screenshare || haveX11ScreensharePlugins_)) - return true; static constexpr std::initializer_list audio_elements = { "audioconvert", @@ -611,10 +623,6 @@ WebRTCSession::havePlugins(bool isVideo, bool isX11Screenshare, std::string *err "videoscale", "vp8enc", }; - static constexpr std::initializer_list screenshare_elements = { - "ximagesink", - "ximagesrc", - }; std::string strError("Missing GStreamer elements: "); GstRegistry *registry = gst_registry_get(); @@ -641,18 +649,35 @@ WebRTCSession::havePlugins(bool isVideo, bool isX11Screenshare, std::string *err // check both elements at once if (isVideo) haveVideoPlugins_ = check_plugins(video_elements); - if (isX11Screenshare) - haveX11ScreensharePlugins_ = check_plugins(screenshare_elements); + + bool haveScreensharePlugins = false; + if (isScreenshare) { + haveScreensharePlugins = check_plugins({"videorate"}); + if (haveScreensharePlugins) { + if (QGuiApplication::platformName() == QStringLiteral("wayland")) { + haveScreensharePlugins = check_plugins({"waylandsink"}); + } else { + haveScreensharePlugins = check_plugins({"ximagesink"}); + } + } + if (haveScreensharePlugins) { + if (screenShareType == ScreenShareType::X11) { + haveScreensharePlugins = check_plugins({"ximagesrc"}); + } else { + haveScreensharePlugins = check_plugins({"pipewiresrc"}); + } + } + } if (!haveVoicePlugins_ || (isVideo && !haveVideoPlugins_) || - (isX11Screenshare && !haveX11ScreensharePlugins_)) { + (isScreenshare && !haveScreensharePlugins)) { nhlog::ui()->error(strError); if (errorMessage) *errorMessage = strError; return false; } - if (isVideo || isX11Screenshare) { + if (isVideo || isScreenshare) { // load qmlglsink to register GStreamer's GstGLVideoItem QML type GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr); gst_object_unref(qmlglsink); @@ -661,12 +686,15 @@ WebRTCSession::havePlugins(bool isVideo, bool isX11Screenshare, std::string *err } bool -WebRTCSession::createOffer(CallType callType, uint32_t shareWindowId) +WebRTCSession::createOffer(CallType callType, + ScreenShareType screenShareType, + uint32_t shareWindowId) { clear(); - isOffering_ = true; - callType_ = callType; - shareWindowId_ = shareWindowId; + isOffering_ = true; + callType_ = callType; + screenShareType_ = screenShareType; + shareWindowId_ = shareWindowId; // opus and vp8 rtp payload types must be defined dynamically // therefore from the range [96-127] @@ -924,6 +952,7 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr); GstElement *tee = gst_element_factory_make("tee", "videosrctee"); gst_bin_add_many(GST_BIN(pipe_), videoconvert, tee, nullptr); + if (callType_ == CallType::VIDEO || (settings->screenSharePiP() && devices_.haveCamera())) { std::pair resolution; std::pair frameRate; @@ -969,16 +998,56 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) nhlog::ui()->debug("WebRTC: screen share hide mouse cursor: {}", settings->screenShareHideCursor()); - GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare"); - if (!ximagesrc) { - nhlog::ui()->error("WebRTC: failed to create ximagesrc"); - return false; + GstElement *screencastsrc = nullptr; + + if (screenShareType_ == ScreenShareType::X11) { + GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare"); + if (!ximagesrc) { + nhlog::ui()->error("WebRTC: failed to create ximagesrc"); + return false; + } + g_object_set(ximagesrc, "use-damage", FALSE, nullptr); + g_object_set(ximagesrc, "xid", shareWindowId_, nullptr); + g_object_set(ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr); + g_object_set(ximagesrc, "do-timestamp", (gboolean)1, nullptr); + + gst_bin_add(GST_BIN(pipe_), ximagesrc); + screencastsrc = ximagesrc; + } else { + ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); + GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", "screenshare"); + if (!pipewiresrc) { + nhlog::ui()->error("WebRTC: failed to create pipewiresrc"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return false; + } + + const ScreenCastPortal::Stream *stream = sc_portal.getStream(); + if (stream == nullptr) { + nhlog::ui()->error("xdg-desktop-portal stream not started"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return false; + } + g_object_set(pipewiresrc, "fd", (gint)stream->fd, nullptr); + std::string path = std::to_string(stream->nodeId); + g_object_set(pipewiresrc, "path", path.c_str(), nullptr); + g_object_set(pipewiresrc, "do-timestamp", (gboolean)1, nullptr); + gst_bin_add(GST_BIN(pipe_), pipewiresrc); + GstElement *videorate = gst_element_factory_make("videorate", nullptr); + gst_bin_add(GST_BIN(pipe_), videorate); + if (!gst_element_link(pipewiresrc, videorate)) { + nhlog::ui()->error("WebRTC: failed to link pipewiresrc -> videorate"); + return false; + } + screencastsrc = videorate; } - g_object_set(ximagesrc, "use-damage", FALSE, nullptr); - g_object_set(ximagesrc, "xid", shareWindowId_, nullptr); - g_object_set(ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr); GstCaps *caps = gst_caps_new_simple("video/x-raw", + "format", + G_TYPE_STRING, + "I420", // For vp8enc "framerate", GST_TYPE_FRACTION, settings->screenShareFrameRate(), @@ -987,13 +1056,13 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr); g_object_set(capsfilter, "caps", caps, nullptr); gst_caps_unref(caps); - gst_bin_add_many(GST_BIN(pipe_), ximagesrc, capsfilter, nullptr); + gst_bin_add(GST_BIN(pipe_), capsfilter); if (settings->screenSharePiP() && devices_.haveCamera()) { GstElement *compositor = gst_element_factory_make("compositor", nullptr); g_object_set(compositor, "background", 1, nullptr); gst_bin_add(GST_BIN(pipe_), compositor); - if (!gst_element_link_many(ximagesrc, compositor, capsfilter, tee, nullptr)) { + if (!gst_element_link_many(screencastsrc, compositor, capsfilter, tee, nullptr)) { nhlog::ui()->error("WebRTC: failed to link screen share elements"); return false; } @@ -1006,7 +1075,7 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) return false; } gst_object_unref(srcpad); - } else if (!gst_element_link_many(ximagesrc, videoconvert, capsfilter, tee, nullptr)) { + } else if (!gst_element_link_many(screencastsrc, videoconvert, capsfilter, tee, nullptr)) { nhlog::ui()->error("WebRTC: failed to link screen share elements"); return false; } @@ -1157,7 +1226,7 @@ WebRTCSession::end() #else bool -WebRTCSession::havePlugins(bool, bool, std::string *) +WebRTCSession::havePlugins(bool, bool, ScreenShareType, std::string *) { return false; } @@ -1171,8 +1240,11 @@ WebRTCSession::haveLocalPiP() const // clang-format off // clang-format < 12 is buggy on this bool -WebRTCSession::createOffer(webrtc::CallType, uint32_t) +WebRTCSession::createOffer(webrtc::CallType, + ScreenShareType screenShareType, + uint32_t) { + (void)screenShareType; return false; } // clang-format on diff --git a/src/voip/WebRTCSession.h b/src/voip/WebRTCSession.h index 49806cca..82753372 100644 --- a/src/voip/WebRTCSession.h +++ b/src/voip/WebRTCSession.h @@ -26,6 +26,13 @@ enum class CallType }; Q_ENUM_NS(CallType) +enum class ScreenShareType +{ + X11, + XDP +}; +Q_ENUM_NS(ScreenShareType) + enum class State { DISCONNECTED, @@ -52,7 +59,10 @@ public: return instance; } - bool havePlugins(bool isVideo, bool isX11Screenshare, std::string *errorMessage = nullptr); + bool havePlugins(bool isVideo, + bool isScreenshare, + webrtc::ScreenShareType screenShareType, + std::string *errorMessage = nullptr); webrtc::CallType callType() const { return callType_; } webrtc::State state() const { return state_; } bool haveLocalPiP() const; @@ -60,7 +70,7 @@ public: bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; } bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; } - bool createOffer(webrtc::CallType, uint32_t shareWindowId); + bool createOffer(webrtc::CallType, webrtc::ScreenShareType, uint32_t shareWindowId); bool acceptOffer(const std::string &sdp); bool acceptAnswer(const std::string &sdp); bool acceptNegotiation(const std::string &sdp); @@ -91,19 +101,19 @@ private: WebRTCSession(); CallDevices &devices_; - bool initialised_ = false; - bool haveVoicePlugins_ = false; - bool haveVideoPlugins_ = false; - bool haveX11ScreensharePlugins_ = false; - webrtc::CallType callType_ = webrtc::CallType::VOICE; - webrtc::State state_ = webrtc::State::DISCONNECTED; - bool isOffering_ = false; - bool isRemoteVideoRecvOnly_ = false; - bool isRemoteVideoSendOnly_ = false; - QQuickItem *videoItem_ = nullptr; - GstElement *pipe_ = nullptr; - GstElement *webrtc_ = nullptr; - unsigned int busWatchId_ = 0; + bool initialised_ = false; + bool haveVoicePlugins_ = false; + bool haveVideoPlugins_ = false; + webrtc::CallType callType_ = webrtc::CallType::VOICE; + webrtc::ScreenShareType screenShareType_ = webrtc::ScreenShareType::X11; + webrtc::State state_ = webrtc::State::DISCONNECTED; + bool isOffering_ = false; + bool isRemoteVideoRecvOnly_ = false; + bool isRemoteVideoSendOnly_ = false; + QQuickItem *videoItem_ = nullptr; + GstElement *pipe_ = nullptr; + GstElement *webrtc_ = nullptr; + unsigned int busWatchId_ = 0; std::vector turnServers_; uint32_t shareWindowId_ = 0; -- cgit 1.4.1 From 562a71a5f3e4e29067103c250c3f2e83d4937d82 Mon Sep 17 00:00:00 2001 From: David Elsing Date: Sun, 12 Mar 2023 11:47:05 +0100 Subject: Add missing license headers --- src/voip/ScreenCastPortal.cpp | 4 ++++ src/voip/ScreenCastPortal.h | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/voip/ScreenCastPortal.cpp b/src/voip/ScreenCastPortal.cpp index 321373d9..41e6c0a2 100644 --- a/src/voip/ScreenCastPortal.cpp +++ b/src/voip/ScreenCastPortal.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #ifdef GSTREAMER_AVAILABLE #include "ScreenCastPortal.h" diff --git a/src/voip/ScreenCastPortal.h b/src/voip/ScreenCastPortal.h index 068dbddb..853ece04 100644 --- a/src/voip/ScreenCastPortal.h +++ b/src/voip/ScreenCastPortal.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #ifdef GSTREAMER_AVAILABLE -- cgit 1.4.1 From 2aadc7c2c49f0b49459600c01d6b2db3cd09dfe5 Mon Sep 17 00:00:00 2001 From: David Elsing Date: Sun, 26 Mar 2023 23:08:43 +0200 Subject: Improve choosing screen share type --- resources/qml/voip/ScreenShare.qml | 38 +++++---------------- src/voip/CallManager.cpp | 67 +++++++++++++++++++++++--------------- src/voip/CallManager.h | 6 ++-- 3 files changed, 53 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index 80ea75d2..1a82a5ce 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -14,10 +14,6 @@ Popup { anchors.centerIn: parent; Component.onCompleted: { - if (CallManager.screenShareX11Available) - CallManager.setScreenShareType(ScreenShareType.X11); - else - CallManager.setScreenShareType(ScreenShareType.XDP); frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); } Component.onDestruction: { @@ -47,29 +43,13 @@ Popup { color: Nheko.colors.windowText } - RadioButton { - id: screenshare_X11 - text: qsTr("X11"); - visible: CallManager.screenShareX11Available - checked: CallManager.screenShareX11Available - onToggled: { - if (screenshare_X11.checked) - CallManager.setScreenShareType(ScreenShareType.X11); - else - CallManager.setScreenShareType(ScreenShareType.XDP); - } - } - RadioButton { - id: screenshare_XDP - text: qsTr("xdg-desktop-portal"); - checked: !CallManager.screenShareX11Available - onToggled: { - if (screenshare_XDP.checked) - CallManager.setScreenShareType(ScreenShareType.XDP); - else - CallManager.setScreenShareType(ScreenShareType.X11); - } - } + ComboBox { + id: screenshareType + + Layout.fillWidth: true + model: CallManager.screenShareTypeList() + onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex); + } } RowLayout { @@ -84,7 +64,7 @@ Popup { } ComboBox { - visible: screenshare_X11.checked + visible: CallManager.screenShareType == ScreenShareType.X11 id: windowCombo Layout.fillWidth: true @@ -92,7 +72,7 @@ Popup { } Button { - visible: screenshare_XDP.checked + visible: CallManager.screenShareType == ScreenShareType.XDP highlighted: !CallManager.screenShareReady text: qsTr("Request screencast") onClicked: { diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp index 4471a0c2..e7bd45e4 100644 --- a/src/voip/CallManager.cpp +++ b/src/voip/CallManager.cpp @@ -68,12 +68,23 @@ CallManager::CallManager(QObject *parent) qRegisterMetaType(); qRegisterMetaType(); - if (screenShareX11Available()) { - screenShareType_ = ScreenShareType::X11; - } else { +#ifdef GSTREAMER_AVAILABLE + std::string errorMessage; + if (session_.havePlugins(true, true, ScreenShareType::XDP, &errorMessage)) { + screenShareTypes_.push_back(ScreenShareType::XDP); screenShareType_ = ScreenShareType::XDP; } + if (std::getenv("DISPLAY")) { + screenShareTypes_.push_back(ScreenShareType::X11); + if (QGuiApplication::platformName() != QStringLiteral("wayland")) { + // Selected by default + screenShareType_ = ScreenShareType::X11; + std::swap(screenShareTypes_[0], screenShareTypes_[1]); + } + } +#endif + connect( &session_, &WebRTCSession::offerCreated, @@ -208,13 +219,6 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); std::string errorMessage; - if (!session_.havePlugins(callType != CallType::VOICE, - callType == CallType::SCREEN, - screenShareType_, - &errorMessage)) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - return; - } callType_ = callType; roomid_ = roomid; @@ -745,16 +749,6 @@ CallManager::callsSupported() #endif } -bool -CallManager::screenShareX11Available() -{ -#ifdef GSTREAMER_AVAILABLE - return std::getenv("DISPLAY"); -#else - return false; -#endif -} - QStringList CallManager::devices(bool isVideo) const { @@ -862,10 +856,30 @@ CallManager::screenShareReady() const #endif } +QStringList +CallManager::screenShareTypeList() +{ + QStringList ret; + ret.reserve(2); + for (ScreenShareType type : screenShareTypes_) { + switch (type) { + case ScreenShareType::X11: + ret.append(tr("X11")); + break; + case ScreenShareType::XDP: + ret.append(tr("Stream from xdg-desktop-portal")); + break; + } + } + + return ret; +} + QStringList CallManager::windowList() { - if (!screenShareX11Available()) { + if (!(std::find(screenShareTypes_.begin(), screenShareTypes_.end(), ScreenShareType::X11) != + screenShareTypes_.end())) { return {}; } @@ -998,7 +1012,7 @@ CallManager::previewWindow(unsigned int index) const } if (screenShareType_ == ScreenShareType::X11 && - (!screenShareX11Available() || windows_.empty() || index >= windows_.size())) { + (windows_.empty() || index >= windows_.size())) { nhlog::ui()->error("X11 screencast not available"); return; } @@ -1085,19 +1099,20 @@ CallManager::setupScreenShareXDP() #ifdef GSTREAMER_AVAILABLE ScreenCastPortal &sc_portal = ScreenCastPortal::instance(); sc_portal.init(); - screenShareType_ = ScreenShareType::XDP; #endif } void -CallManager::setScreenShareType(webrtc::ScreenShareType screenShareType) +CallManager::setScreenShareType(unsigned int index) { #ifdef GSTREAMER_AVAILABLE closeScreenShare(); - screenShareType_ = screenShareType; + if (index >= screenShareTypes_.size()) + nhlog::ui()->error("WebRTC: Screen share type index out of range"); + screenShareType_ = screenShareTypes_[index]; emit screenShareChanged(); #else - (void)screenShareType; + (void)index; #endif } diff --git a/src/voip/CallManager.h b/src/voip/CallManager.h index 5d7ebda9..bbc7a903 100644 --- a/src/voip/CallManager.h +++ b/src/voip/CallManager.h @@ -44,7 +44,6 @@ class CallManager final : public QObject Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged) Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged) Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) - Q_PROPERTY(bool screenShareX11Available READ screenShareX11Available CONSTANT) Q_PROPERTY(bool screenShareReady READ screenShareReady NOTIFY screenShareChanged) public: @@ -68,7 +67,6 @@ public: bool screenShareReady() const; static bool callsSupported(); - static bool screenShareX11Available(); public slots: void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0); @@ -80,8 +78,9 @@ public slots: mtx::events::voip::CallHangUp::Reason = mtx::events::voip::CallHangUp::Reason::UserHangUp); void rejectInvite(); void setupScreenShareXDP(); - void setScreenShareType(webrtc::ScreenShareType); + void setScreenShareType(unsigned int index); void closeScreenShare(); + QStringList screenShareTypeList(); QStringList windowList(); void previewWindow(unsigned int windowIndex) const; @@ -126,6 +125,7 @@ private: std::vector turnURIs_; QTimer turnServerTimer_; QMediaPlayer player_; + std::vector screenShareTypes_; std::vector> windows_; std::vector rejectCallPartyIDs_; -- cgit 1.4.1 From 7f98cd01330057ade7cb7f5703b8ca896ecc36ab Mon Sep 17 00:00:00 2001 From: David Elsing Date: Mon, 27 Mar 2023 16:42:29 +0200 Subject: Use mtx::client::utils::random_token for XDP session tokens --- src/voip/ScreenCastPortal.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/voip/ScreenCastPortal.cpp b/src/voip/ScreenCastPortal.cpp index 41e6c0a2..cce5a375 100644 --- a/src/voip/ScreenCastPortal.cpp +++ b/src/voip/ScreenCastPortal.cpp @@ -14,22 +14,13 @@ #include #include #include +#include #include static QString make_token() { - thread_local std::random_device rng; - std::uniform_int_distribution index_dist(0, 9); - - std::string token; - token.reserve(5 + 64); - token += "nheko"; - - for (uint8_t i = 0; i < 64; ++i) - token.push_back('0' + index_dist(rng)); - - return QString::fromStdString(std::move(token)); + return QString::fromStdString("nheko" + mtx::client::utils::random_token(64, false)); } static QString -- cgit 1.4.1 From c3081ece401bec8b14ba962a5120fe92f4166577 Mon Sep 17 00:00:00 2001 From: David Elsing Date: Wed, 29 Mar 2023 23:01:53 +0200 Subject: Cleanup Qt D-Bus connections and watchers --- src/voip/CallManager.cpp | 2 +- src/voip/ScreenCastPortal.cpp | 270 +++++++++++++++++++++++++----------------- src/voip/ScreenCastPortal.h | 10 +- src/voip/WebRTCSession.cpp | 2 +- 4 files changed, 171 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp index e7bd45e4..d9866c2c 100644 --- a/src/voip/CallManager.cpp +++ b/src/voip/CallManager.cpp @@ -1061,7 +1061,7 @@ CallManager::previewWindow(unsigned int index) const return; } GstElement *pipewiresrc = gst_element_factory_make("pipewiresrc", nullptr); - g_object_set(pipewiresrc, "fd", (gint)stream->fd, nullptr); + g_object_set(pipewiresrc, "fd", (gint)stream->fd.fileDescriptor(), nullptr); std::string path = std::to_string(stream->nodeId); g_object_set(pipewiresrc, "path", path.c_str(), nullptr); g_object_set(pipewiresrc, "do-timestamp", (gboolean)1, nullptr); diff --git a/src/voip/ScreenCastPortal.cpp b/src/voip/ScreenCastPortal.cpp index cce5a375..31cddba0 100644 --- a/src/voip/ScreenCastPortal.cpp +++ b/src/voip/ScreenCastPortal.cpp @@ -34,6 +34,48 @@ handle_path(QString handle_token) QStringLiteral("/") + handle_token; } +bool +ScreenCastPortal::makeConnection(QString service, + QString path, + QString interface, + QString name, + const char *slot) +{ + if (QDBusConnection::sessionBus().connect(service, path, interface, name, this, slot)) { + last_connection = { + std::move(service), std::move(path), std::move(interface), std::move(name), slot}; + return true; + } + return false; +} + +void +ScreenCastPortal::disconnectClose() +{ + QDBusConnection::sessionBus().disconnect(QStringLiteral("org.freedesktop.portal.Desktop"), + sessionHandle.path(), + QStringLiteral("org.freedesktop.portal.Session"), + QStringLiteral("Closed"), + this, + SLOT(closedHandler(QVariantMap))); +} + +void +ScreenCastPortal::removeConnection() +{ + if (!last_connection.has_value()) + return; + + const auto &connection = *last_connection; + QDBusConnection::sessionBus().disconnect(connection[0], + connection[1], + connection[2], + connection[3], + this, + connection[4].toLocal8Bit().data()); + last_connection = std::nullopt; +} + void ScreenCastPortal::init() { @@ -79,13 +121,19 @@ ScreenCastPortal::close(bool reinit) break; case State::Starting: if (!reinit) { - // Remaining handler will abort. + disconnectClose(); + removeConnection(); state = State::Closed; } break; case State::Started: { state = State::Closing; + disconnectClose(); + // Close file descriptor if it was opened + stream = Stream{}; + emit readyChanged(); + auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), sessionHandle.path(), QStringLiteral("org.freedesktop.portal.Session"), @@ -97,7 +145,9 @@ ScreenCastPortal::close(bool reinit) &QDBusPendingCallWatcher::finished, this, [this, reinit](QDBusPendingCallWatcher *self) { + self->deleteLater(); QDBusPendingReply reply = *self; + if (!reply.isValid()) { nhlog::ui()->warn("org.freedesktop.portal.ScreenCast (Close): {}", reply.error().message().toStdString()); @@ -116,8 +166,11 @@ ScreenCastPortal::close(bool reinit) void ScreenCastPortal::closedHandler(uint response, const QVariantMap &) { + removeConnection(); + disconnectClose(); + if (response != 0) { - nhlog::ui()->error("org.freedekstop.portal.ScreenCast (Closed): {}", response); + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (Closed): {}", response); } nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: Connection closed"); @@ -130,12 +183,16 @@ ScreenCastPortal::createSession() { // Connect before sending the request to avoid missing the reply QString handle_token = make_token(); - QDBusConnection::sessionBus().connect(QStringLiteral("org.freedesktop.portal.Desktop"), - handle_path(handle_token), - QStringLiteral("org.freedesktop.portal.Request"), - QStringLiteral("Response"), - this, - SLOT(createSessionHandler(uint, QVariantMap))); + if (!makeConnection(QStringLiteral("org.freedesktop.portal.Desktop"), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + SLOT(createSessionHandler(uint, QVariantMap)))) { + nhlog::ui()->error( + "Connection to signal Response for org.freedesktop.portal.Request failed"); + close(); + return; + } auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), @@ -145,11 +202,11 @@ ScreenCastPortal::createSession() {QStringLiteral("session_handle_token"), make_token()}}; QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { - QDBusPendingReply reply = *self; self->deleteLater(); + QDBusPendingReply reply = *self; if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.portal.ScreenCast (CreateSession): {}", @@ -162,31 +219,32 @@ ScreenCastPortal::createSession() void ScreenCastPortal::createSessionHandler(uint response, const QVariantMap &results) { - switch (state) { - case State::Closed: + removeConnection(); + + if (state != State::Starting) { nhlog::ui()->warn("ScreenCastPortal not starting"); - break; - case State::Starting: { - if (response != 0) { - nhlog::ui()->error("org.freedekstop.portal.ScreenCast (CreateSession Response): {}", - response); - close(); - return; - } + return; + } + if (response != 0) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (CreateSession Response): {}", + response); + close(); + return; + } - sessionHandle = QDBusObjectPath(results.value(QStringLiteral("session_handle")).toString()); + sessionHandle = QDBusObjectPath(results.value(QStringLiteral("session_handle")).toString()); - nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: sessionHandle = {}", - sessionHandle.path().toStdString()); + nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: sessionHandle = {}", + sessionHandle.path().toStdString()); - getAvailableSourceTypes(); - } break; - case State::Started: - nhlog::ui()->warn("ScreenCastPortal already started"); - break; - case State::Closing: - break; - } + QDBusConnection::sessionBus().connect(QStringLiteral("org.freedesktop.portal.Desktop"), + sessionHandle.path(), + QStringLiteral("org.freedesktop.portal.Session"), + QStringLiteral("Closed"), + this, + SLOT(closedHandler(QVariantMap))); + + getAvailableSourceTypes(); } void @@ -200,11 +258,11 @@ ScreenCastPortal::getAvailableSourceTypes() << QStringLiteral("AvailableSourceTypes"); QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { - QDBusPendingReply reply = *self; self->deleteLater(); + QDBusPendingReply reply = *self; if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.DBus.Properties (Get AvailableSourceTypes): {}", @@ -213,29 +271,21 @@ ScreenCastPortal::getAvailableSourceTypes() return; } - switch (state) { - case State::Closed: + if (state != State::Starting) { nhlog::ui()->warn("ScreenCastPortal not starting"); - break; - case State::Starting: { - const auto &value = reply.value().variant(); - if (value.canConvert()) { - availableSourceTypes = value.value(); - } else { - nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " - "AvailableSourceTypes)"); - close(); - return; - } - - getAvailableCursorModes(); - } break; - case State::Started: - nhlog::ui()->warn("ScreenCastPortal already started"); - break; - case State::Closing: - break; + return; + } + const auto &value = reply.value().variant(); + if (value.canConvert()) { + availableSourceTypes = value.value(); + } else { + nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " + "AvailableSourceTypes)"); + close(); + return; } + + getAvailableCursorModes(); }); } @@ -250,11 +300,11 @@ ScreenCastPortal::getAvailableCursorModes() << QStringLiteral("AvailableCursorModes"); QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(msg); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { - QDBusPendingReply reply = *self; self->deleteLater(); + QDBusPendingReply reply = *self; if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.DBus.Properties (Get AvailableCursorModes): {}", @@ -263,29 +313,21 @@ ScreenCastPortal::getAvailableCursorModes() return; } - switch (state) { - case State::Closed: + if (state != State::Starting) { nhlog::ui()->warn("ScreenCastPortal not starting"); - break; - case State::Starting: { - const auto &value = reply.value().variant(); - if (value.canConvert()) { - availableCursorModes = value.value(); - } else { - nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " - "AvailableCursorModes)"); - close(); - return; - } - - selectSources(); - } break; - case State::Started: - nhlog::ui()->warn("ScreenCastPortal already started"); - break; - case State::Closing: - break; + return; + } + const auto &value = reply.value().variant(); + if (value.canConvert()) { + availableCursorModes = value.value(); + } else { + nhlog::ui()->error("Invalid reply from org.freedesktop.DBus.Properties (Get " + "AvailableCursorModes)"); + close(); + return; } + + selectSources(); }); } @@ -294,12 +336,16 @@ ScreenCastPortal::selectSources() { // Connect before sending the request to avoid missing the reply auto handle_token = make_token(); - QDBusConnection::sessionBus().connect(QString(), - handle_path(handle_token), - QStringLiteral("org.freedesktop.portal.Request"), - QStringLiteral("Response"), - this, - SLOT(selectSourcesHandler(uint, QVariantMap))); + if (!makeConnection(QString(), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + SLOT(selectSourcesHandler(uint, QVariantMap)))) { + nhlog::ui()->error( + "Connection to signal Response for org.freedesktop.portal.Request failed"); + close(); + return; + } auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), @@ -321,7 +367,9 @@ ScreenCastPortal::selectSources() QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + self->deleteLater(); QDBusPendingReply reply = *self; + if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.portal.ScreenCast (SelectSources): {}", reply.error().message().toStdString()); @@ -333,25 +381,19 @@ ScreenCastPortal::selectSources() void ScreenCastPortal::selectSourcesHandler(uint response, const QVariantMap &) { - switch (state) { - case State::Closed: + removeConnection(); + + if (state != State::Starting) { nhlog::ui()->warn("ScreenCastPortal not starting"); - break; - case State::Starting: { - if (response != 0) { - nhlog::ui()->error("org.freedekstop.portal.ScreenCast (SelectSources Response): {}", - response); - close(); - return; - } - start(); - } break; - case State::Started: - nhlog::ui()->warn("ScreenCastPortal already started"); - break; - case State::Closing: - break; + return; + } + if (response != 0) { + nhlog::ui()->error("org.freedesktop.portal.ScreenCast (SelectSources Response): {}", + response); + close(); + return; } + start(); } void @@ -359,12 +401,15 @@ ScreenCastPortal::start() { // Connect before sending the request to avoid missing the reply auto handle_token = make_token(); - QDBusConnection::sessionBus().connect(QString(), - handle_path(handle_token), - QStringLiteral("org.freedesktop.portal.Request"), - QStringLiteral("Response"), - this, - SLOT(startHandler(uint, QVariantMap))); + if (!makeConnection(QString(), + handle_path(handle_token), + QStringLiteral("org.freedesktop.portal.Request"), + QStringLiteral("Response"), + SLOT(startHandler(uint, QVariantMap)))) { + nhlog::ui()->error("Connection to org.freedesktop.portal.Request Response failed"); + close(); + return; + } auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), @@ -377,11 +422,12 @@ ScreenCastPortal::start() QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + self->deleteLater(); QDBusPendingReply reply = *self; + if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.portal.ScreenCast (Start): {}", reply.error().message().toStdString()); - } else { } }); } @@ -416,6 +462,8 @@ operator>>(const QDBusArgument &argument, PipeWireStream &stream) void ScreenCastPortal::startHandler(uint response, const QVariantMap &results) { + removeConnection(); + if (response != 0) { nhlog::ui()->error("org.freedesktop.portal.ScreenCast (Start Response): {}", response); close(); @@ -448,15 +496,17 @@ ScreenCastPortal::openPipeWireRemote() QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); connect( watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + self->deleteLater(); QDBusPendingReply reply = *self; + if (!reply.isValid()) { nhlog::ui()->error("org.freedesktop.portal.ScreenCast (OpenPipeWireRemote): {}", reply.error().message().toStdString()); close(); } else { - stream.fd = reply.value().fileDescriptor(); - nhlog::ui()->debug("org.freedesktop.portal.ScreenCast: fd = {}", stream.fd); - + stream.fd = std::move(reply.value()); + nhlog::ui()->error("org.freedesktop.portal.ScreenCast: fd = {}", + stream.fd.fileDescriptor()); state = State::Started; emit readyChanged(); } diff --git a/src/voip/ScreenCastPortal.h b/src/voip/ScreenCastPortal.h index 853ece04..bc1fd143 100644 --- a/src/voip/ScreenCastPortal.h +++ b/src/voip/ScreenCastPortal.h @@ -20,7 +20,7 @@ class ScreenCastPortal final : public QObject public: struct Stream { - int fd; + QDBusUnixFileDescriptor fd; quint32 nodeId; }; @@ -51,6 +51,13 @@ private: void selectSources(); void start(); void openPipeWireRemote(); + bool makeConnection(QString service, + QString path, + QString interface, + QString name, + const char *slot); + void removeConnection(); + void disconnectClose(); QDBusObjectPath sessionHandle; uint availableSourceTypes; uint availableCursorModes; @@ -65,6 +72,7 @@ private: Closing, }; State state = State::Closed; + std::optional> last_connection; }; #endif diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index ba75c744..c0cab4ac 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -1030,7 +1030,7 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) pipe_ = nullptr; return false; } - g_object_set(pipewiresrc, "fd", (gint)stream->fd, nullptr); + g_object_set(pipewiresrc, "fd", (gint)stream->fd.fileDescriptor(), nullptr); std::string path = std::to_string(stream->nodeId); g_object_set(pipewiresrc, "path", path.c_str(), nullptr); g_object_set(pipewiresrc, "do-timestamp", (gboolean)1, nullptr); -- cgit 1.4.1 From c8402b156b4d19a7528b95fffc994b32beceabff Mon Sep 17 00:00:00 2001 From: David Elsing Date: Thu, 30 Mar 2023 10:14:11 +0200 Subject: Change text of the PipeWire streenshare method --- src/voip/CallManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp index d9866c2c..993937f0 100644 --- a/src/voip/CallManager.cpp +++ b/src/voip/CallManager.cpp @@ -867,7 +867,7 @@ CallManager::screenShareTypeList() ret.append(tr("X11")); break; case ScreenShareType::XDP: - ret.append(tr("Stream from xdg-desktop-portal")); + ret.append(tr("PipeWire")); break; } } -- cgit 1.4.1 From 90b8542a2e987ebad3774d8fd27e47d050022d9c Mon Sep 17 00:00:00 2001 From: David Elsing Date: Thu, 30 Mar 2023 17:36:06 +0200 Subject: Include missing header --- src/voip/ScreenCastPortal.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/voip/ScreenCastPortal.h b/src/voip/ScreenCastPortal.h index bc1fd143..4d1e028e 100644 --- a/src/voip/ScreenCastPortal.h +++ b/src/voip/ScreenCastPortal.h @@ -12,6 +12,7 @@ #include #include #include +#include class ScreenCastPortal final : public QObject { -- cgit 1.4.1