summary refs log tree commit diff
path: root/src/CallManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/CallManager.cpp')
-rw-r--r--src/CallManager.cpp224
1 files changed, 202 insertions, 22 deletions
diff --git a/src/CallManager.cpp b/src/CallManager.cpp

index 7acd9592..6d41f1c6 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp
@@ -1,7 +1,13 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include <algorithm> #include <cctype> #include <chrono> #include <cstdint> +#include <cstdlib> +#include <memory> #include <QMediaPlaylist> #include <QUrl> @@ -17,6 +23,18 @@ #include "mtx/responses/turn_server.hpp" +#ifdef XCB_AVAILABLE +#include <xcb/xcb.h> +#include <xcb/xcb_ewmh.h> +#endif + +#ifdef GSTREAMER_AVAILABLE +extern "C" +{ +#include "gst/gst.h" +} +#endif + Q_DECLARE_METATYPE(std::vector<mtx::events::msg::CallCandidates::Candidate>) Q_DECLARE_METATYPE(mtx::events::msg::CallCandidates::Candidate) Q_DECLARE_METATYPE(mtx::responses::TurnServer) @@ -24,6 +42,8 @@ Q_DECLARE_METATYPE(mtx::responses::TurnServer) using namespace mtx::events; using namespace mtx::events::msg; +using webrtc::CallType; + namespace { std::vector<std::string> getTurnURIs(const mtx::responses::TurnServer &turnServer); @@ -44,8 +64,8 @@ CallManager::CallManager(QObject *parent) this, [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) { nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_); - emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_}); - emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); + emit newMessage(roomid_, CallInvite{callid_, sdp, "0", timeoutms_}); + emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); std::string callid(callid_); QTimer::singleShot(timeoutms_, this, [this, callid]() { if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) { @@ -62,8 +82,8 @@ CallManager::CallManager(QObject *parent) this, [this](const std::string &sdp, const std::vector<CallCandidates::Candidate> &candidates) { nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_); - emit newMessage(roomid_, CallAnswer{callid_, sdp, 0}); - emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); + emit newMessage(roomid_, CallAnswer{callid_, sdp, "0"}); + emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); }); connect(&session_, @@ -71,7 +91,7 @@ CallManager::CallManager(QObject *parent) this, [this](const CallCandidates::Candidate &candidate) { nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_); - emit newMessage(roomid_, CallCandidates{callid_, {candidate}, 0}); + emit newMessage(roomid_, CallCandidates{callid_, {candidate}, "0"}); }); connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer); @@ -148,10 +168,18 @@ CallManager::CallManager(QObject *parent) } void -CallManager::sendInvite(const QString &roomid, bool isVideo) +CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int windowIndex) { if (isOnCall()) return; + if (callType == CallType::SCREEN) { + if (!screenShareSupported()) + return; + if (windows_.empty() || windowIndex >= windows_.size()) { + nhlog::ui()->error("WebRTC: window index out of range"); + return; + } + } auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); if (roomInfo.member_count != 2) { @@ -161,17 +189,20 @@ CallManager::sendInvite(const QString &roomid, bool isVideo) std::string errorMessage; if (!session_.havePlugins(false, &errorMessage) || - (isVideo && !session_.havePlugins(true, &errorMessage))) { + ((callType == CallType::VIDEO || callType == CallType::SCREEN) && + !session_.havePlugins(true, &errorMessage))) { emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); return; } - isVideo_ = isVideo; - roomid_ = roomid; + callType_ = callType; + roomid_ = roomid; session_.setTurnServers(turnURIs_); generateCallID(); - nhlog::ui()->debug( - "WebRTC: call id: {} - creating {} invite", callid_, isVideo ? "video" : "voice"); + std::string strCallType = callType_ == CallType::VOICE + ? "voice" + : (callType_ == CallType::VIDEO ? "video" : "screen"); + nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType); std::vector<RoomMember> members(cache::getMembers(roomid.toStdString())); const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front(); @@ -179,7 +210,8 @@ CallManager::sendInvite(const QString &roomid, bool isVideo) callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); emit newInviteState(); playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true); - if (!session_.createOffer(isVideo)) { + if (!session_.createOffer( + callType, callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) { emit ChatPage::instance()->showNotification("Problem setting up call."); endCall(); } @@ -206,7 +238,7 @@ CallManager::hangUp(CallHangUp::Reason reason) if (!callid_.empty()) { nhlog::ui()->debug( "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason)); - emit newMessage(roomid_, CallHangUp{callid_, 0, reason}); + emit newMessage(roomid_, CallHangUp{callid_, "0", reason}); endCall(); } } @@ -215,8 +247,8 @@ void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) { #ifdef GSTREAMER_AVAILABLE - if (handleEvent_<CallInvite>(event) || handleEvent_<CallCandidates>(event) || - handleEvent_<CallAnswer>(event) || handleEvent_<CallHangUp>(event)) + if (handleEvent<CallInvite>(event) || handleEvent<CallCandidates>(event) || + handleEvent<CallAnswer>(event) || handleEvent<CallHangUp>(event)) return; #else (void)event; @@ -225,7 +257,7 @@ CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) template<typename T> bool -CallManager::handleEvent_(const mtx::events::collections::TimelineEvents &event) +CallManager::handleEvent(const mtx::events::collections::TimelineEvents &event) { if (std::holds_alternative<RoomEvent<T>>(event)) { handleEvent(std::get<RoomEvent<T>>(event)); @@ -259,7 +291,7 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent) if (isOnCall() || roomInfo.member_count != 2) { emit newMessage(QString::fromStdString(callInviteEvent.room_id), CallHangUp{callInviteEvent.content.call_id, - 0, + "0", CallHangUp::Reason::InviteTimeOut}); return; } @@ -280,9 +312,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent) callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); haveCallInvite_ = true; - isVideo_ = isVideo; + callType_ = isVideo ? CallType::VIDEO : CallType::VOICE; inviteSDP_ = callInviteEvent.content.sdp; - CallDevices::instance().refresh(); emit newInviteState(); } @@ -295,7 +326,7 @@ CallManager::acceptInvite() stopRingtone(); std::string errorMessage; if (!session_.havePlugins(false, &errorMessage) || - (isVideo_ && !session_.havePlugins(true, &errorMessage))) { + (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) { emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); hangUp(); return; @@ -383,7 +414,7 @@ CallManager::toggleMicMute() } bool -CallManager::callsSupported() const +CallManager::callsSupported() { #ifdef GSTREAMER_AVAILABLE return true; @@ -392,6 +423,12 @@ CallManager::callsSupported() const #endif } +bool +CallManager::screenShareSupported() +{ + return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY"); +} + QStringList CallManager::devices(bool isVideo) const { @@ -424,7 +461,7 @@ CallManager::clear() callParty_.clear(); callPartyAvatarUrl_.clear(); callid_.clear(); - isVideo_ = false; + callType_ = CallType::VOICE; haveCallInvite_ = false; emit newInviteState(); inviteSDP_.clear(); @@ -477,6 +514,149 @@ CallManager::stopRingtone() player_.setPlaylist(nullptr); } +QStringList +CallManager::windowList() +{ + windows_.clear(); + windows_.push_back({tr("Entire screen"), 0}); + +#ifdef XCB_AVAILABLE + std::unique_ptr<xcb_connection_t, std::function<void(xcb_connection_t *)>> connection( + xcb_connect(nullptr, nullptr), [](xcb_connection_t *c) { xcb_disconnect(c); }); + if (xcb_connection_has_error(connection.get())) { + nhlog::ui()->error("Failed to connect to X server"); + return {}; + } + + xcb_ewmh_connection_t ewmh; + if (!xcb_ewmh_init_atoms_replies( + &ewmh, xcb_ewmh_init_atoms(connection.get(), &ewmh), nullptr)) { + nhlog::ui()->error("Failed to connect to EWMH server"); + return {}; + } + std::unique_ptr<xcb_ewmh_connection_t, std::function<void(xcb_ewmh_connection_t *)>> + ewmhconnection(&ewmh, [](xcb_ewmh_connection_t *c) { xcb_ewmh_connection_wipe(c); }); + + for (int i = 0; i < ewmh.nb_screens; i++) { + xcb_ewmh_get_windows_reply_t clients; + if (!xcb_ewmh_get_client_list_reply( + &ewmh, xcb_ewmh_get_client_list(&ewmh, i), &clients, nullptr)) { + nhlog::ui()->error("Failed to request window list"); + return {}; + } + + for (uint32_t w = 0; w < clients.windows_len; w++) { + xcb_window_t window = clients.windows[w]; + + std::string name; + xcb_ewmh_get_utf8_strings_reply_t data; + auto getName = [](xcb_ewmh_get_utf8_strings_reply_t *r) { + std::string name(r->strings, r->strings_len); + xcb_ewmh_get_utf8_strings_reply_wipe(r); + return name; + }; + + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&ewmh, window); + if (xcb_ewmh_get_wm_name_reply(&ewmh, cookie, &data, nullptr)) + name = getName(&data); + + cookie = xcb_ewmh_get_wm_visible_name(&ewmh, window); + if (xcb_ewmh_get_wm_visible_name_reply(&ewmh, cookie, &data, nullptr)) + name = getName(&data); + + windows_.push_back({QString::fromStdString(name), window}); + } + xcb_ewmh_get_windows_reply_wipe(&clients); + } +#endif + QStringList ret; + ret.reserve(windows_.size()); + for (const auto &w : windows_) + ret.append(w.first); + + return ret; +} + +#ifdef GSTREAMER_AVAILABLE +namespace { + +GstElement *pipe_ = nullptr; +unsigned int 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; + } + break; + default: + break; + } + return TRUE; +} +} +#endif + +void +CallManager::previewWindow(unsigned int index) const +{ +#ifdef GSTREAMER_AVAILABLE + if (windows_.empty() || index >= windows_.size() || !gst_is_initialized()) + return; + + GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr); + if (!ximagesrc) { + nhlog::ui()->error("Failed to create ximagesrc"); + return; + } + 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); + + 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); + + GstCaps *caps = gst_caps_new_simple( + "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, 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); + if (!gst_element_link_many( + ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, 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_); + pipe_ = nullptr; + return; + } + + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_)); + busWatchId_ = gst_bus_add_watch(bus, newBusMessage, nullptr); + gst_object_unref(bus); +#else + (void)index; +#endif +} + namespace { std::vector<std::string> getTurnURIs(const mtx::responses::TurnServer &turnServer)