From 99f752390b68f190b825fde135dcd8e830460157 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Thu, 25 Apr 2024 17:25:06 +0200 Subject: [PATCH] Screensharing + Mac ARM64 + Linux build --- CMakeLists.txt | 3 +- Linphone/CMakeLists.txt | 17 ++ Linphone/application_info.cmake | 2 +- Linphone/config.h.cmake | 1 + Linphone/core/App.cpp | 15 + Linphone/core/CMakeLists.txt | 6 + Linphone/core/call/CallCore.cpp | 24 ++ Linphone/core/call/CallCore.hpp | 11 +- Linphone/core/conference/ConferenceCore.cpp | 52 ++-- Linphone/core/conference/ConferenceCore.hpp | 14 + Linphone/core/screen/ScreenList.cpp | 76 +++++ Linphone/core/screen/ScreenList.hpp | 51 ++++ Linphone/core/screen/ScreenProxy.cpp | 41 +++ Linphone/core/screen/ScreenProxy.hpp | 46 +++ .../videoSource/VideoSourceDescriptorCore.cpp | 99 +++++++ .../videoSource/VideoSourceDescriptorCore.hpp | 72 +++++ .../videoSource/VideoSourceDescriptorGui.cpp | 38 +++ .../videoSource/VideoSourceDescriptorGui.hpp | 41 +++ Linphone/model/CMakeLists.txt | 2 + Linphone/model/call/CallModel.cpp | 26 ++ Linphone/model/call/CallModel.hpp | 7 + Linphone/model/conference/ConferenceModel.cpp | 62 ++++ Linphone/model/conference/ConferenceModel.hpp | 17 +- .../VideoSourceDescriptorModel.cpp | 82 ++++++ .../VideoSourceDescriptorModel.hpp | 58 ++++ Linphone/tool/CMakeLists.txt | 22 ++ Linphone/tool/LinphoneEnums.cpp | 10 + Linphone/tool/LinphoneEnums.hpp | 11 + Linphone/tool/native/DesktopTools.hpp | 36 +++ Linphone/tool/native/DesktopToolsLinux.cpp | 265 ++++++++++++++++++ Linphone/tool/native/DesktopToolsLinux.hpp | 71 +++++ Linphone/tool/native/DesktopToolsMacOs.cpp | 43 +++ Linphone/tool/native/DesktopToolsMacOs.hpp | 62 ++++ .../tool/native/DesktopToolsMacOsNative.mm | 228 +++++++++++++++ Linphone/tool/native/DesktopToolsWindows.cpp | 188 +++++++++++++ Linphone/tool/native/DesktopToolsWindows.hpp | 70 +++++ .../native/screen-saver/ScreenSaverDBus.cpp | 75 +++++ .../native/screen-saver/ScreenSaverDBus.hpp | 50 ++++ .../native/screen-saver/ScreenSaverMacOs.m | 49 ++++ .../native/screen-saver/ScreenSaverXdg.cpp | 56 ++++ .../native/screen-saver/ScreenSaverXdg.hpp | 44 +++ .../native/state-process/StateProcessMacOs.mm | 20 ++ Linphone/tool/providers/ScreenProvider.cpp | 118 ++++++++ Linphone/tool/providers/ScreenProvider.hpp | 55 ++++ Linphone/view/App/CallsWindow.qml | 3 +- .../view/Item/Call/Menu/ScreencastPanel.qml | 125 +++++++-- Linphone/view/Item/TabBar.qml | 14 +- .../view/Layout/Call/ActiveSpeakerLayout.qml | 11 +- cmake/GenerateAppMacos.cmake | 3 + cmake/TasksMacos.cmake | 18 +- cmake/install/cleanCPack.cmake.in | 2 + cmake/install/install.cmake | 8 +- cmake/install/macos/Info.plist.in | 2 + cmake/toolchains/toolchain-mac-arm64.cmake | 2 +- external/linphone-sdk | 2 +- 55 files changed, 2452 insertions(+), 74 deletions(-) create mode 100644 Linphone/core/screen/ScreenList.cpp create mode 100644 Linphone/core/screen/ScreenList.hpp create mode 100644 Linphone/core/screen/ScreenProxy.cpp create mode 100644 Linphone/core/screen/ScreenProxy.hpp create mode 100644 Linphone/core/videoSource/VideoSourceDescriptorCore.cpp create mode 100644 Linphone/core/videoSource/VideoSourceDescriptorCore.hpp create mode 100644 Linphone/core/videoSource/VideoSourceDescriptorGui.cpp create mode 100644 Linphone/core/videoSource/VideoSourceDescriptorGui.hpp create mode 100644 Linphone/model/videoSource/VideoSourceDescriptorModel.cpp create mode 100644 Linphone/model/videoSource/VideoSourceDescriptorModel.hpp create mode 100644 Linphone/tool/native/DesktopTools.hpp create mode 100644 Linphone/tool/native/DesktopToolsLinux.cpp create mode 100644 Linphone/tool/native/DesktopToolsLinux.hpp create mode 100644 Linphone/tool/native/DesktopToolsMacOs.cpp create mode 100644 Linphone/tool/native/DesktopToolsMacOs.hpp create mode 100644 Linphone/tool/native/DesktopToolsMacOsNative.mm create mode 100644 Linphone/tool/native/DesktopToolsWindows.cpp create mode 100644 Linphone/tool/native/DesktopToolsWindows.hpp create mode 100644 Linphone/tool/native/screen-saver/ScreenSaverDBus.cpp create mode 100644 Linphone/tool/native/screen-saver/ScreenSaverDBus.hpp create mode 100644 Linphone/tool/native/screen-saver/ScreenSaverMacOs.m create mode 100644 Linphone/tool/native/screen-saver/ScreenSaverXdg.cpp create mode 100644 Linphone/tool/native/screen-saver/ScreenSaverXdg.hpp create mode 100644 Linphone/tool/native/state-process/StateProcessMacOs.mm create mode 100644 Linphone/tool/providers/ScreenProvider.cpp create mode 100644 Linphone/tool/providers/ScreenProvider.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8c6685f..cb821e004 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ endif() set(CMAKE_INSTALL_PREFIX "${APPLICATION_OUTPUT_DIR}") if( APPLE ) - set(LINPHONEAPP_MACOS_ARCHS "x86_64" CACHE STRING "MacOS architectures to build: comma-separated list of values in [arm64, x86_64]") + set(LINPHONEAPP_MACOS_ARCHS "arm64" CACHE STRING "MacOS architectures to build: comma-separated list of values in [arm64, x86_64]") set(LINPHONESDK_BUILD_TYPE "Default")#Using Mac will remove all SDK targets. set(ENABLE_FAT_BINARY "ON") # Disable XCFrameworks as it is not supported. @@ -149,6 +149,7 @@ add_option(OPTION_LIST ENABLE_QT_KEYCHAIN "Build QtKeychain to manage VFS from S add_option(OPTION_LIST ENABLE_QRCODE "Enable QRCode support" OFF)#Experimental add_option(OPTION_LIST ENABLE_RELATIVE_PREFIX "Set Internal packages relative to the binary" ON) add_option(OPTION_LIST ENABLE_SANITIZER "Enable sanitizer." OFF) +add_option(OPTION_LIST ENABLE_SCREENSHARING "Enable screen sharing." ON) add_option(OPTION_LIST ENABLE_STRICT "Build with strict compilator flags e.g. -Wall -Werror" OFF) add_option(OPTION_LIST ENABLE_TESTS "Build with testing binaries of SDK" OFF) add_option(OPTION_LIST ENABLE_TESTS_COMPONENTS "Build libbctoolbox-tester" OFF) diff --git a/Linphone/CMakeLists.txt b/Linphone/CMakeLists.txt index fd2a70d52..d49127d62 100644 --- a/Linphone/CMakeLists.txt +++ b/Linphone/CMakeLists.txt @@ -27,6 +27,14 @@ endif() find_package(Qt6 REQUIRED COMPONENTS Core) find_package(Qt6 REQUIRED COMPONENTS ${QT_PACKAGES}) +if(NOT WIN32) + find_package(X11) +endif() + +if(X11_FOUND) + list(APPEND APP_TARGETS X11::X11) +endif() + ################################################################ # CONFIGS ################################################################ @@ -132,6 +140,15 @@ if(MSVC) set_target_properties(${TARGET_NAME} PROPERTIES PDB_NAME "${EXECUTABLE_NAME}_app") endif() +if(WIN32) + if(ENABLE_SCREENSHARING) + target_link_libraries(${TARGET_NAME} PUBLIC Dwmapi) + endif() +elseif (APPLE) + target_link_libraries(${TARGET_NAME} PUBLIC "-framework Cocoa" "-framework IOKit" "-framework AVFoundation" "-framework ScreenCaptureKit") +endif() + + foreach(T ${APP_TARGETS}) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC $) target_link_libraries(${TARGET_NAME} PUBLIC ${T}) diff --git a/Linphone/application_info.cmake b/Linphone/application_info.cmake index 537300013..acc498d93 100644 --- a/Linphone/application_info.cmake +++ b/Linphone/application_info.cmake @@ -1,5 +1,5 @@ set(APPLICATION_DESCRIPTION "A libre SIP client") -set(APPLICATION_ID "com.belledonnecommunications.linphone") +set(APPLICATION_ID "com.belledonnecommunications.linphone60") set(APPLICATION_NAME Linphone60) set(APPLICATION_URL "https://www.linphone.org") set(APPLICATION_VENDOR "Belledonne Communications") diff --git a/Linphone/config.h.cmake b/Linphone/config.h.cmake index a8ce73734..1c6dda0d6 100644 --- a/Linphone/config.h.cmake +++ b/Linphone/config.h.cmake @@ -33,6 +33,7 @@ #cmakedefine EXECUTABLE_NAME "${EXECUTABLE_NAME}" #cmakedefine MSPLUGINS_DIR "${MSPLUGINS_DIR}" #cmakedefine ENABLE_APP_WEBVIEW "${ENABLE_APP_WEBVIEW}" +#cmakedefine ENABLE_SCREENSHARING "${ENABLE_SCREENSHARING}" #cmakedefine QTKEYCHAIN_TARGET_NAME ${QTKEYCHAIN_TARGET_NAME} #cmakedefine PDF_ENABLED diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 18274667a..a3347dff5 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -57,18 +57,23 @@ #include "core/participant/ParticipantProxy.hpp" #include "core/phone-number/PhoneNumber.hpp" #include "core/phone-number/PhoneNumberProxy.hpp" +#include "core/screen/ScreenList.hpp" +#include "core/screen/ScreenProxy.hpp" #include "core/search/MagicSearchProxy.hpp" #include "core/setting/SettingsCore.hpp" #include "core/singleapplication/singleapplication.h" #include "core/timezone/TimeZone.hpp" #include "core/timezone/TimeZoneProxy.hpp" #include "core/variant/VariantList.hpp" +#include "core/videoSource/VideoSourceDescriptorGui.hpp" #include "model/object/VariantObject.hpp" #include "tool/Constants.hpp" #include "tool/EnumsToString.hpp" #include "tool/Utils.hpp" +#include "tool/native/DesktopTools.hpp" #include "tool/providers/AvatarProvider.hpp" #include "tool/providers/ImageProvider.hpp" +#include "tool/providers/ScreenProvider.hpp" #include "tool/thread/Thread.hpp" DEFINE_ABSTRACT_OBJECT(App) @@ -158,6 +163,9 @@ void App::init() { initCppInterfaces(); mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); + mEngine->addImageProvider(ScreenProvider::ProviderId, new ScreenProvider()); + mEngine->addImageProvider(WindowProvider::ProviderId, new WindowProvider()); + mEngine->addImageProvider(WindowIconProvider::ProviderId, new WindowIconProvider()); // Enable notifications. mNotifier = new Notifier(mEngine); @@ -254,6 +262,13 @@ void App::initCppInterfaces() { qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy"); + qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "ScreenList", QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "ScreenProxy"); + + qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorCore", + QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui"); + LinphoneEnums::registerMetaTypes(); } diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 448fa75cc..a906494ea 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -56,6 +56,12 @@ list(APPEND _LINPHONEAPP_SOURCES core/participant/ParticipantDeviceProxy.cpp core/participant/ParticipantList.cpp core/participant/ParticipantProxy.cpp + + core/screen/ScreenList.cpp + core/screen/ScreenProxy.cpp + + core/videoSource/VideoSourceDescriptorCore.cpp + core/videoSource/VideoSourceDescriptorGui.cpp ) ## Single Application diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index 47826d526..eb3c0322d 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -102,6 +102,8 @@ CallCore::CallCore(const std::shared_ptr &call) : QObject(nullpt mMicrophoneVolume = call->getRecordVolume(); mRecordable = mState == LinphoneEnums::CallState::StreamsRunning; mConferenceVideoLayout = mCallModel->getConferenceVideoLayout(); + auto videoSource = call->getVideoSource(); + mVideoSourceDescriptor = VideoSourceDescriptorCore::create(videoSource ? videoSource->clone() : nullptr); } CallCore::~CallCore() { @@ -299,6 +301,17 @@ void CallCore::setSelf(QSharedPointer me) { &CallCore::lSetConferenceVideoLayout, [this](LinphoneEnums::ConferenceLayout layout) { mCallModelConnection->invokeToModel([this, layout]() { mCallModel->changeConferenceVideoLayout(layout); }); }); + mCallModelConnection->makeConnectToCore( + &CallCore::lSetVideoSourceDescriptor, [this](VideoSourceDescriptorGui *gui) { + mCallModelConnection->invokeToModel( + [this, model = gui->getCore()->getModel()]() { mCallModel->setVideoSourceDescriptorModel(model); }); + }); + + mCallModelConnection->makeConnectToModel(&CallModel::videoDescriptorChanged, [this]() { + auto videoSource = mCallModel->getMonitor()->getVideoSource(); + auto core = VideoSourceDescriptorCore::create(videoSource ? videoSource->clone() : nullptr); + mCallModelConnection->invokeToCore([this, core]() { setVideoSourceDescriptor(core); }); + }); } QString CallCore::getPeerAddress() const { @@ -568,6 +581,17 @@ LinphoneEnums::ConferenceLayout CallCore::getConferenceVideoLayout() const { return mConferenceVideoLayout; } +VideoSourceDescriptorGui *CallCore::getVideoSourceDescriptorGui() const { + return new VideoSourceDescriptorGui(mVideoSourceDescriptor); +} + +void CallCore::setVideoSourceDescriptor(QSharedPointer core) { + if (mVideoSourceDescriptor != core) { + mVideoSourceDescriptor = core; + emit videoSourceDescriptorChanged(); + } +} + void CallCore::setConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout) { mustBeInMainThread(log().arg(Q_FUNC_INFO)); if (mConferenceVideoLayout != layout) { diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index ddd64f2da..05a91c998 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -23,6 +23,7 @@ #include "core/conference/ConferenceCore.hpp" #include "core/conference/ConferenceGui.hpp" +#include "core/videoSource/VideoSourceDescriptorGui.hpp" #include "model/call/CallModel.hpp" #include "tool/LinphoneEnums.hpp" #include "tool/thread/SafeConnection.hpp" @@ -66,6 +67,9 @@ class CallCore : public QObject, public AbstractObject { Q_PROPERTY(LinphoneEnums::ConferenceLayout conferenceVideoLayout READ getConferenceVideoLayout WRITE lSetConferenceVideoLayout NOTIFY conferenceVideoLayoutChanged) + Q_PROPERTY(VideoSourceDescriptorGui *videoSourceDescriptor READ getVideoSourceDescriptorGui WRITE + lSetVideoSourceDescriptor NOTIFY videoSourceDescriptorChanged) + public: // Should be call from model Thread. Will be automatically in App thread after initialization static QSharedPointer create(const std::shared_ptr &call); @@ -149,8 +153,10 @@ public: LinphoneEnums::ConferenceLayout getConferenceVideoLayout() const; void setConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout); - std::shared_ptr getModel() const; + VideoSourceDescriptorGui *getVideoSourceDescriptorGui() const; + void setVideoSourceDescriptor(QSharedPointer core); + std::shared_ptr getModel() const; signals: void statusChanged(LinphoneEnums::CallStatus status); void stateChanged(LinphoneEnums::CallState state); @@ -174,6 +180,7 @@ signals: void microphoneVolumeGainChanged(); void conferenceChanged(); void conferenceVideoLayoutChanged(); + void videoSourceDescriptorChanged(); // Linphone commands void lAccept(bool withVideo); // Accept an incoming call @@ -194,6 +201,7 @@ signals: void lSetInputAudioDevice(QString id); void lSetOutputAudioDevice(QString id); void lSetConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout); + void lSetVideoSourceDescriptor(VideoSourceDescriptorGui *gui); /* TODO Q_INVOKABLE void acceptWithVideo(); @@ -216,6 +224,7 @@ signals: private: std::shared_ptr mCallModel; QSharedPointer mConference; + QSharedPointer mVideoSourceDescriptor; LinphoneEnums::CallStatus mStatus; LinphoneEnums::CallState mState; LinphoneEnums::CallState mTransferState; diff --git a/Linphone/core/conference/ConferenceCore.cpp b/Linphone/core/conference/ConferenceCore.cpp index 4373c82f5..f4d3f011e 100644 --- a/Linphone/core/conference/ConferenceCore.cpp +++ b/Linphone/core/conference/ConferenceCore.cpp @@ -39,6 +39,8 @@ ConferenceCore::ConferenceCore(const std::shared_ptr &conf mConferenceModel = ConferenceModel::create(conference); mSubject = Utils::coreStringToAppString(conference->getSubject()); mParticipantDeviceCount = conference->getParticipantDeviceList().size(); + mIsLocalScreenSharing = mConferenceModel->isLocalScreenSharing(); + mIsScreenSharingEnabled = mConferenceModel->isScreenSharingEnabled(); auto me = conference->getMe(); if (me) { mMe = ParticipantCore::create(me); @@ -57,27 +59,27 @@ void ConferenceCore::setSelf(QSharedPointer me) { [this](const std::shared_ptr &participantDevice) { auto device = ParticipantDeviceCore::create(participantDevice); mConferenceModelConnection->invokeToCore([this, device]() { setActiveSpeaker(device); }); - }); - mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantDeviceAdded, [this]() { - int count = mConferenceModel->getParticipantDeviceCount(); - mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); - }); - mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantDeviceRemoved, [this]() { - int count = mConferenceModel->getParticipantDeviceCount(); - mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); - }); - mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantAdded, [this]() { - int count = mConferenceModel->getParticipantDeviceCount(); - mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); - }); - mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantRemoved, [this]() { - int count = mConferenceModel->getParticipantDeviceCount(); - mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); - }); + }); + mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantDeviceStateChanged, [this]() { int count = mConferenceModel->getParticipantDeviceCount(); mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); }); + + mConferenceModelConnection->makeConnectToModel(&ConferenceModel::participantDeviceCountChanged, [this](int count) { + mConferenceModelConnection->invokeToCore([this, count]() { setParticipantDeviceCount(count); }); + }); + mConferenceModelConnection->makeConnectToModel(&ConferenceModel::isLocalScreenSharingChanged, [this]() { + auto state = mConferenceModel->isLocalScreenSharing(); + mConferenceModelConnection->invokeToCore([this, state]() { setIsLocalScreenSharing(state); }); + }); + mConferenceModelConnection->makeConnectToModel(&ConferenceModel::isScreenSharingEnabledChanged, [this]() { + auto state = mConferenceModel->isScreenSharingEnabled(); + mConferenceModelConnection->invokeToCore([this, state]() { setIsScreenSharingEnabled(state); }); + }); + mConferenceModelConnection->makeConnectToCore(&ConferenceCore::lToggleScreenSharing, [this]() { + mConferenceModelConnection->invokeToModel([this]() { mConferenceModel->toggleScreenSharing(); }); + }); } bool ConferenceCore::updateLocalParticipant() { // true if changed @@ -117,6 +119,22 @@ void ConferenceCore::setIsReady(bool state) { } } +void ConferenceCore::setIsLocalScreenSharing(bool state) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mIsLocalScreenSharing != state) { + mIsLocalScreenSharing = state; + emit isLocalScreenSharingChanged(); + } +} + +void ConferenceCore::setIsScreenSharingEnabled(bool state) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mIsScreenSharingEnabled != state) { + mIsScreenSharingEnabled = state; + emit isScreenSharingEnabledChanged(); + } +} + std::shared_ptr ConferenceCore::getModel() const { return mConferenceModel; } diff --git a/Linphone/core/conference/ConferenceCore.hpp b/Linphone/core/conference/ConferenceCore.hpp index be234b86c..efab38382 100644 --- a/Linphone/core/conference/ConferenceCore.hpp +++ b/Linphone/core/conference/ConferenceCore.hpp @@ -40,7 +40,12 @@ public: // Q_PROPERTY(ParticipantDeviceList *participantDevices READ getParticipantDeviceList CONSTANT) // Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged) Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged) + Q_PROPERTY(QString subject MEMBER mSubject CONSTANT) + Q_PROPERTY(bool isLocalScreenSharing MEMBER mIsLocalScreenSharing WRITE setIsLocalScreenSharing NOTIFY + isLocalScreenSharingChanged) + Q_PROPERTY(bool isScreenSharingEnabled MEMBER mIsScreenSharingEnabled WRITE setIsScreenSharingEnabled NOTIFY + isScreenSharingEnabledChanged) Q_PROPERTY(int participantDeviceCount READ getParticipantDeviceCount NOTIFY participantDeviceCountChanged) Q_PROPERTY(ParticipantDeviceGui *activeSpeaker READ getActiveSpeakerGui NOTIFY activeSpeakerChanged) Q_PROPERTY(ParticipantGui *me READ getMeGui) @@ -66,15 +71,22 @@ public: void setIsReady(bool state); + void setIsLocalScreenSharing(bool state); + void setIsScreenSharingEnabled(bool state); + std::shared_ptr getModel() const; //--------------------------------------------------------------------------- signals: void isReadyChanged(); + void isLocalScreenSharingChanged(); + void isScreenSharingEnabledChanged(); void participantDeviceCountChanged(); void activeSpeakerChanged(); + void lToggleScreenSharing(); + private: QSharedPointer> mConferenceModelConnection; std::shared_ptr mConferenceModel; @@ -83,6 +95,8 @@ private: int mParticipantDeviceCount = 0; bool mIsReady = false; + bool mIsLocalScreenSharing = false; + bool mIsScreenSharingEnabled = false; QString mSubject; QDateTime mStartDate = QDateTime::currentDateTime(); diff --git a/Linphone/core/screen/ScreenList.cpp b/Linphone/core/screen/ScreenList.cpp new file mode 100644 index 000000000..1f70e793c --- /dev/null +++ b/Linphone/core/screen/ScreenList.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenList.hpp" +#include +#include +#include +#include + +#include "tool/native/DesktopTools.hpp" + +ScreenList::ScreenList(QObject *parent) : AbstractListProxy(parent) { + connect(this, &ScreenList::modeChanged, this, &ScreenList::update); +} + +ScreenList::~ScreenList() { +} + +ScreenList::Mode ScreenList::getMode() const { + return mMode; +} + +void ScreenList::setMode(ScreenList::Mode data) { + if (mMode != data) { + mMode = data; + emit modeChanged(); + } +} + +void ScreenList::update() { + beginResetModel(); + mList.clear(); + if (mMode == WINDOWS) { + auto list = DesktopTools::getWindows(); + qDebug() << list; + for (auto l : list) + mList << l; + } else { + auto list = QGuiApplication::screens(); + for (auto l : list) { + QVariantMap w; + w["name"] = l->name(); + w["screenIndex"] = mList.size(); + mList << w; + } + } + + endResetModel(); +} + +QVariant ScreenList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) { + QVariantMap data = mList[row]; + return data; + } + return QVariant(); +} diff --git a/Linphone/core/screen/ScreenList.hpp b/Linphone/core/screen/ScreenList.hpp new file mode 100644 index 000000000..65c78a3e6 --- /dev/null +++ b/Linphone/core/screen/ScreenList.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREEN_LIST_H_ +#define SCREEN_LIST_H_ + +#include "core/proxy/AbstractListProxy.hpp" + +#include + +class ScreenList : public AbstractListProxy { + + Q_OBJECT + +public: + enum Mode { SCREENS = 0, WINDOWS = 1 }; + Q_ENUM(Mode) + + ScreenList(QObject *parent = Q_NULLPTR); + virtual ~ScreenList(); + + ScreenList::Mode getMode() const; + void setMode(ScreenList::Mode); + + void update(); + + Mode mMode = WINDOWS; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +signals: + void modeChanged(); +}; + +#endif diff --git a/Linphone/core/screen/ScreenProxy.cpp b/Linphone/core/screen/ScreenProxy.cpp new file mode 100644 index 000000000..3b7ba3338 --- /dev/null +++ b/Linphone/core/screen/ScreenProxy.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "ScreenList.hpp" +#include "ScreenProxy.hpp" +// ============================================================================= + +ScreenProxy::ScreenProxy(QObject *parent) : SortFilterProxy(parent) { + setSourceModel(new ScreenList(this)); + sort(0); +} +ScreenList::Mode ScreenProxy::getMode() const { + return dynamic_cast(sourceModel())->getMode(); +} + +void ScreenProxy::setMode(ScreenList::Mode data) { + dynamic_cast(sourceModel())->setMode(data); +} + +void ScreenProxy::update() { + dynamic_cast(sourceModel())->update(); +} diff --git a/Linphone/core/screen/ScreenProxy.hpp b/Linphone/core/screen/ScreenProxy.hpp new file mode 100644 index 000000000..365b7e8fc --- /dev/null +++ b/Linphone/core/screen/ScreenProxy.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREEN_PROXY_H_ +#define SCREEN_PROXY_H_ + +#include "ScreenList.hpp" +#include "core/proxy/SortFilterProxy.hpp" +// ============================================================================= + +class QWindow; + +class ScreenProxy : public SortFilterProxy { + class ScreenModelFilter; + + Q_OBJECT + Q_PROPERTY(ScreenList::Mode mode READ getMode WRITE setMode NOTIFY modeChanged) +public: + ScreenProxy(QObject *parent = Q_NULLPTR); + + ScreenList::Mode getMode() const; + void setMode(ScreenList::Mode); + + Q_INVOKABLE void update(); +signals: + void modeChanged(); +}; + +#endif diff --git a/Linphone/core/videoSource/VideoSourceDescriptorCore.cpp b/Linphone/core/videoSource/VideoSourceDescriptorCore.cpp new file mode 100644 index 000000000..e1d9cbc0f --- /dev/null +++ b/Linphone/core/videoSource/VideoSourceDescriptorCore.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "VideoSourceDescriptorCore.hpp" +#include "core/App.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorCore) + +QSharedPointer +VideoSourceDescriptorCore::create(const std::shared_ptr &desc) { + auto sharedPointer = + QSharedPointer(new VideoSourceDescriptorCore(desc), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +VideoSourceDescriptorCore::VideoSourceDescriptorCore(const std::shared_ptr &desc) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mustBeInLinphoneThread(getClassName()); + mVideoDescModel = Utils::makeQObject_ptr(desc); + mScreenIndex = mVideoDescModel->getScreenSharingIndex(); + mWindowId = mVideoDescModel->getWindowId(); +} + +VideoSourceDescriptorCore::~VideoSourceDescriptorCore() { +} + +void VideoSourceDescriptorCore::setSelf(QSharedPointer me) { + mVideoDescModelConnection = QSharedPointer>( + new SafeConnection(me, mVideoDescModel), + &QObject::deleteLater); + mVideoDescModelConnection->makeConnectToCore(&VideoSourceDescriptorCore::lSetWindowId, [this](quint64 id) { + mVideoDescModelConnection->invokeToModel([this, id]() { mVideoDescModel->setScreenSharingWindow((void *)id); }); + }); + mVideoDescModelConnection->makeConnectToCore(&VideoSourceDescriptorCore::lSetScreenIndex, [this](int index) { + mVideoDescModelConnection->invokeToModel([this, index]() { mVideoDescModel->setScreenSharingDisplay(index); }); + }); + + mVideoDescModelConnection->makeConnectToModel(&VideoSourceDescriptorModel::videoDescriptorChanged, [this]() { + auto id = mVideoDescModel->getWindowId(); + auto index = mVideoDescModel->getScreenSharingIndex(); + mVideoDescModelConnection->invokeToCore([this, id, index]() { + setWindowId(id); + setScreenSharingDisplay(index); + }); + }); +} + +quint64 VideoSourceDescriptorCore::getWindowId() const { + return mWindowId; +} + +void VideoSourceDescriptorCore::setWindowId(quint64 id) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mWindowId != id) { + mWindowId = id; + if (mWindowId != 0 && mScreenIndex >= 0) setScreenSharingDisplay(-1); + emit windowIdChanged(); + } +} + +int VideoSourceDescriptorCore::getScreenSharingIndex() const { + return mScreenIndex; +} + +void VideoSourceDescriptorCore::setScreenSharingDisplay(int data) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mScreenIndex != data) { + mScreenIndex = data; + if (mScreenIndex >= 0 && mWindowId != 0) setWindowId(0); + emit screenIndexChanged(); + } +} + +std::shared_ptr VideoSourceDescriptorCore::getModel() { + return mVideoDescModel; +} diff --git a/Linphone/core/videoSource/VideoSourceDescriptorCore.hpp b/Linphone/core/videoSource/VideoSourceDescriptorCore.hpp new file mode 100644 index 000000000..33744ade8 --- /dev/null +++ b/Linphone/core/videoSource/VideoSourceDescriptorCore.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIDEO_SOURCE_DESCRIPTOR_CORE_H_ +#define VIDEO_SOURCE_DESCRIPTOR_CORE_H_ + +#include +// ============================================================================= +#include +#include +#include +#include + +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" + +#include "model/videoSource/VideoSourceDescriptorModel.hpp" + +class VideoSourceDescriptorCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(int screenSharingIndex READ getScreenSharingIndex WRITE lSetScreenIndex NOTIFY screenIndexChanged) + Q_PROPERTY(quint64 windowId READ getWindowId WRITE lSetWindowId NOTIFY windowIdChanged) +public: + static QSharedPointer + create(const std::shared_ptr &desc); + VideoSourceDescriptorCore(const std::shared_ptr &desc); + virtual ~VideoSourceDescriptorCore(); + void setSelf(QSharedPointer me); + + int getScreenSharingIndex() const; + void setScreenSharingDisplay(int index); + + quint64 getWindowId() const; + void setWindowId(quint64 id); + + std::shared_ptr getModel(); + +signals: + void videoDescriptorChanged(); + void windowIdChanged(); + void screenIndexChanged(); + + void lSetWindowId(quint64 windowId); + void lSetScreenIndex(int index); + +private: + int mScreenIndex = 0; + quint64 mWindowId = 0; + std::shared_ptr mVideoDescModel; + QSharedPointer> mVideoDescModelConnection; + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(VideoSourceDescriptorCore *) +#endif diff --git a/Linphone/core/videoSource/VideoSourceDescriptorGui.cpp b/Linphone/core/videoSource/VideoSourceDescriptorGui.cpp new file mode 100644 index 000000000..6c3b069d8 --- /dev/null +++ b/Linphone/core/videoSource/VideoSourceDescriptorGui.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "VideoSourceDescriptorGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorGui) + +VideoSourceDescriptorGui::VideoSourceDescriptorGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +VideoSourceDescriptorGui::~VideoSourceDescriptorGui() { + mustBeInMainThread("~" + getClassName()); +} + +VideoSourceDescriptorCore *VideoSourceDescriptorGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/videoSource/VideoSourceDescriptorGui.hpp b/Linphone/core/videoSource/VideoSourceDescriptorGui.hpp new file mode 100644 index 000000000..20f7e84ae --- /dev/null +++ b/Linphone/core/videoSource/VideoSourceDescriptorGui.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIDEO_SOURCE_DESCRIPTOR_GUI_H_ +#define VIDEO_SOURCE_DESCRIPTOR_GUI_H_ + +#include "VideoSourceDescriptorCore.hpp" +#include +#include + +class VideoSourceDescriptorGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(VideoSourceDescriptorCore *core READ getCore CONSTANT) + +public: + VideoSourceDescriptorGui(QSharedPointer core); + ~VideoSourceDescriptorGui(); + VideoSourceDescriptorCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index 8f06d6128..11638fcbd 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -30,6 +30,8 @@ list(APPEND _LINPHONEAPP_SOURCES model/setting/SettingsModel.cpp model/tool/ToolModel.cpp + + model/videoSource/VideoSourceDescriptorModel.cpp ) set(_LINPHONEAPP_SOURCES ${_LINPHONEAPP_SOURCES} PARENT_SCOPE) diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index 93b34bd8b..e0d0fe332 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -330,6 +330,32 @@ void CallModel::updateConferenceVideoLayout() { } } +void CallModel::setVideoSource(std::shared_ptr videoDesc) { + mMonitor->setVideoSource(videoDesc); + + emit videoDescriptorChanged(); +} + +LinphoneEnums::VideoSourceScreenSharingType CallModel::getVideoSourceType() const { + auto videoSource = mMonitor->getVideoSource(); + return LinphoneEnums::fromLinphone(videoSource ? videoSource->getScreenSharingType() + : linphone::VideoSourceScreenSharingType::Display); +} +int CallModel::getScreenSharingIndex() const { + auto videoSource = mMonitor->getVideoSource(); + if (videoSource && videoSource->getScreenSharingType() == linphone::VideoSourceScreenSharingType::Display) { + void *t = videoSource->getScreenSharing(); + return *(int *)(&t); + } else return -1; +} + +void CallModel::setVideoSourceDescriptorModel(std::shared_ptr model) { + if (model) setVideoSource(model->mDesc); + else { + setVideoSource(nullptr); + } +} + void CallModel::onDtmfReceived(const std::shared_ptr &call, int dtmf) { emit dtmfReceived(call, dtmf); } diff --git a/Linphone/model/call/CallModel.hpp b/Linphone/model/call/CallModel.hpp index 57d5e5a8d..7faa97aa7 100644 --- a/Linphone/model/call/CallModel.hpp +++ b/Linphone/model/call/CallModel.hpp @@ -22,6 +22,7 @@ #define CALL_MODEL_H_ #include "model/listener/Listener.hpp" +#include "model/videoSource/VideoSourceDescriptorModel.hpp" #include "tool/AbstractObject.hpp" #include "tool/LinphoneEnums.hpp" @@ -72,6 +73,11 @@ public: void changeConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout); // Make a call request void updateConferenceVideoLayout(); // Called from call state changed ater the new layout has been set. + void setVideoSource(std::shared_ptr videoDesc); + LinphoneEnums::VideoSourceScreenSharingType getVideoSourceType() const; + int getScreenSharingIndex() const; + void setVideoSourceDescriptorModel(std::shared_ptr model = nullptr); + static void activateLocalVideo(std::shared_ptr ¶ms, const std::shared_ptr ¤tParams, bool enable); @@ -92,6 +98,7 @@ signals: void outputAudioDeviceChanged(const std::string &id); void conferenceChanged(); void conferenceVideoLayoutChanged(LinphoneEnums::ConferenceLayout layout); + void videoDescriptorChanged(); private: QTimer mDurationTimer; diff --git a/Linphone/model/conference/ConferenceModel.cpp b/Linphone/model/conference/ConferenceModel.cpp index 9f40cd2fe..e5f8f2e42 100644 --- a/Linphone/model/conference/ConferenceModel.cpp +++ b/Linphone/model/conference/ConferenceModel.cpp @@ -24,6 +24,7 @@ #include "core/path/Paths.hpp" #include "model/core/CoreModel.hpp" +#include "model/tool/ToolModel.hpp" #include "tool/Utils.hpp" DEFINE_ABSTRACT_OBJECT(ConferenceModel) @@ -38,6 +39,8 @@ ConferenceModel::ConferenceModel(const std::shared_ptr &co : ::Listener(conference, parent) { mustBeInLinphoneThread(getClassName()); lDebug() << "[ConferenceModel] new" << this << conference.get(); + connect(this, &ConferenceModel::isScreenSharingEnabledChanged, this, + &ConferenceModel::onIsScreenSharingEnabledChanged); } ConferenceModel::~ConferenceModel() { @@ -132,6 +135,32 @@ std::shared_ptr ConferenceModel::getOutputAudioDevi return mMonitor->getOutputAudioDevice(); } +void ConferenceModel::toggleScreenSharing() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto device = mMonitor->getScreenSharingParticipantDevice(); + + if (!device || ToolModel::isLocal(mMonitor, device)) { + bool enable = !device; + auto params = CoreModel::getInstance()->getCore()->createCallParams(mMonitor->getCall()); + params->enableScreenSharing(enable); + if (enable) { + params->setConferenceVideoLayout(linphone::Conference::Layout::ActiveSpeaker); + params->enableVideo(true); + } + if (params->isValid()) mMonitor->getCall()->update(params); + else lCritical() << log().arg("Cannot toggle screen sharing because parameters are invalid"); + } +} + +bool ConferenceModel::isLocalScreenSharing() const { + auto device = mMonitor->getScreenSharingParticipantDevice(); + return device && ToolModel::isLocal(mMonitor, device); +} + +bool ConferenceModel::isScreenSharingEnabled() const { + return mMonitor && mMonitor->getScreenSharingParticipant(); +} + void ConferenceModel::onActiveSpeakerParticipantDevice( const std::shared_ptr &conference, const std::shared_ptr &participantDevice) { @@ -143,10 +172,12 @@ void ConferenceModel::onActiveSpeakerParticipantDevice( void ConferenceModel::onParticipantAdded(const std::shared_ptr &conference, const std::shared_ptr &participant) { emit participantAdded(participant); + emit participantDeviceCountChanged(getParticipantDeviceCount()); } void ConferenceModel::onParticipantRemoved(const std::shared_ptr &conference, const std::shared_ptr &participant) { emit participantRemoved(participant); + emit participantDeviceCountChanged(getParticipantDeviceCount()); } void ConferenceModel::onParticipantDeviceAdded(const std::shared_ptr &conference, const std::shared_ptr &participantDevice) { @@ -156,6 +187,7 @@ void ConferenceModel::onParticipantDeviceAdded(const std::shared_ptrgetMe()->getDevices()) lDebug() << "\t--> " << d->getAddress()->asString().c_str(); emit participantDeviceAdded(participantDevice); + emit participantDeviceCountChanged(getParticipantDeviceCount()); } void ConferenceModel::onParticipantDeviceRemoved( const std::shared_ptr &conference, @@ -163,7 +195,9 @@ void ConferenceModel::onParticipantDeviceRemoved( lDebug() << "onParticipantDeviceRemoved: " << participantDevice->getAddress()->asString().c_str() << " isInConf?[" << participantDevice->isInConference() << "]"; lDebug() << "Me devices : " << conference->getMe()->getDevices().size(); + if (participantDevice->screenSharingEnabled()) emit isScreenSharingEnabledChanged(false); emit participantDeviceRemoved(participantDevice); + emit participantDeviceCountChanged(getParticipantDeviceCount()); } void ConferenceModel::onParticipantDeviceStateChanged(const std::shared_ptr &conference, const std::shared_ptr &device, @@ -201,9 +235,28 @@ void ConferenceModel::onParticipantDeviceIsSpeakingChanged( // ". Speaking:" << isSpeaking; emit participantDeviceIsSpeakingChanged(participantDevice, isSpeaking); } + +void ConferenceModel::onParticipantDeviceScreenSharingChanged( + const std::shared_ptr &conference, + const std::shared_ptr &device, + bool enabled) { + qDebug() << "onParticipantDeviceScreenSharingChanged: " << device->getAddress()->asString().c_str() + << ". Enabled:" << enabled; + emit participantDeviceScreenSharingChanged(device, enabled); + if (ToolModel::isLocal(mMonitor, device)) { + emit isLocalScreenSharingChanged(enabled); + } + emit isScreenSharingEnabledChanged(enabled); +} + void ConferenceModel::onStateChanged(const std::shared_ptr &conference, linphone::Conference::State newState) { lDebug() << "onStateChanged:" << (int)newState; + if (newState == linphone::Conference::State::Created) { + emit participantDeviceCountChanged(mMonitor->getParticipantDeviceList().size()); + if (mMonitor->getScreenSharingParticipant()) emit isScreenSharingEnabledChanged(true); + } + // updateLocalParticipant(); emit conferenceStateChanged(newState); } void ConferenceModel::onSubjectChanged(const std::shared_ptr &conference, @@ -215,3 +268,12 @@ void ConferenceModel::onAudioDeviceChanged(const std::shared_ptr &audioDevice) { lDebug() << "onAudioDeviceChanged is not yet implemented."; } + +void ConferenceModel::onIsScreenSharingEnabledChanged() { + auto call = mMonitor->getCall(); + std::shared_ptr params = CoreModel::getInstance()->getCore()->createCallParams(call); + if (params->getConferenceVideoLayout() == linphone::Conference::Layout::Grid && params->videoEnabled()) { + params->setConferenceVideoLayout(linphone::Conference::Layout::ActiveSpeaker); + } + call->update(params); +} diff --git a/Linphone/model/conference/ConferenceModel.hpp b/Linphone/model/conference/ConferenceModel.hpp index 94a2e61f8..317a13310 100644 --- a/Linphone/model/conference/ConferenceModel.hpp +++ b/Linphone/model/conference/ConferenceModel.hpp @@ -49,6 +49,10 @@ public: void setOutputAudioDevice(const std::shared_ptr &id); std::shared_ptr getOutputAudioDevice() const; + void toggleScreenSharing(); + bool isLocalScreenSharing() const; + bool isScreenSharingEnabled() const; + void setPaused(bool paused); void removeParticipant(const std::shared_ptr &p); @@ -57,6 +61,8 @@ public: int getParticipantDeviceCount() const; + void onIsScreenSharingEnabledChanged(); + signals: void microphoneMutedChanged(bool isMuted); void speakerMutedChanged(bool isMuted); @@ -70,6 +76,9 @@ signals: void microphoneVolumeGainChanged(float volume); void inputAudioDeviceChanged(const std::string &id); void outputAudioDeviceChanged(const std::string &id); + void isLocalScreenSharingChanged(bool enabled); + void isScreenSharingEnabledChanged(bool enabled); + void participantDeviceCountChanged(int count); private: // LINPHONE LISTENERS @@ -102,12 +111,16 @@ private: onParticipantDeviceIsSpeakingChanged(const std::shared_ptr &conference, const std::shared_ptr &participantDevice, bool isSpeaking) override; + virtual void + onParticipantDeviceScreenSharingChanged(const std::shared_ptr &conference, + const std::shared_ptr &device, + bool enabled) override; virtual void onStateChanged(const std::shared_ptr &conference, linphone::Conference::State newState) override; virtual void onSubjectChanged(const std::shared_ptr &conference, const std::string &subject) override; virtual void onAudioDeviceChanged(const std::shared_ptr &conference, - const std::shared_ptr &audioDevice) override; + const std::shared_ptr &audioDevice) override; signals: void activeSpeakerParticipantDevice(const std::shared_ptr &participantDevice); @@ -125,6 +138,8 @@ signals: const std::shared_ptr &participantDevice); void participantDeviceIsSpeakingChanged(const std::shared_ptr &participantDevice, bool isSpeaking); + void participantDeviceScreenSharingChanged(const std::shared_ptr &device, + bool enabled); void conferenceStateChanged(linphone::Conference::State newState); void subjectChanged(const std::string &subject); diff --git a/Linphone/model/videoSource/VideoSourceDescriptorModel.cpp b/Linphone/model/videoSource/VideoSourceDescriptorModel.cpp new file mode 100644 index 000000000..bcc0527a1 --- /dev/null +++ b/Linphone/model/videoSource/VideoSourceDescriptorModel.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "VideoSourceDescriptorModel.hpp" +#include "tool/native/DesktopTools.hpp" +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorModel) + +VideoSourceDescriptorModel::VideoSourceDescriptorModel() { +} +VideoSourceDescriptorModel::VideoSourceDescriptorModel(std::shared_ptr desc) { + mDesc = desc; + if (mDesc && mDesc->getScreenSharingType() == linphone::VideoSourceScreenSharingType::Display) + mScreenIndex = DesktopTools::getDisplayIndex(mDesc->getScreenSharing()); +} + +void VideoSourceDescriptorModel::setScreenSharingDisplay(int index) { + if (!mDesc) mDesc = linphone::Factory::get()->createVideoSourceDescriptor(); + mScreenIndex = index; + mDesc->setScreenSharing(linphone::VideoSourceScreenSharingType::Display, DesktopTools::getDisplay(index)); + emit videoDescriptorChanged(); +} + +void VideoSourceDescriptorModel::setScreenSharingWindow(void *window) { // Get data from DesktopTools. + if (!mDesc) mDesc = linphone::Factory::get()->createVideoSourceDescriptor(); + else if (getVideoSourceType() == LinphoneEnums::VideoSourceScreenSharingTypeWindow && window == getScreenSharing()) + return; + mDesc->setScreenSharing(linphone::VideoSourceScreenSharingType::Window, window); + emit videoDescriptorChanged(); +} + +void *VideoSourceDescriptorModel::getScreenSharing() const { + if (!mDesc) return nullptr; + else return mDesc->getScreenSharing(); +} + +quint64 VideoSourceDescriptorModel::getWindowId() const { + if (!mDesc) return 0; + else { + void *temp = mDesc->getScreenSharing(); + return *(quint64 *)&temp; + } +} + +void VideoSourceDescriptorModel::setWindowId(quint64 id) { + if (getWindowId() != id) { + setScreenSharingWindow((void *)id); + } +} + +bool VideoSourceDescriptorModel::isScreenSharing() const { + return mDesc && mDesc->getType() == linphone::VideoSourceType::ScreenSharing; +} + +LinphoneEnums::VideoSourceScreenSharingType VideoSourceDescriptorModel::getVideoSourceType() const { + return mDesc ? LinphoneEnums::fromLinphone(mDesc->getScreenSharingType()) + : LinphoneEnums::VideoSourceScreenSharingType::VideoSourceScreenSharingTypeDisplay; +} + +int VideoSourceDescriptorModel::getScreenSharingIndex() const { + if (mDesc && mDesc->getScreenSharingType() == linphone::VideoSourceScreenSharingType::Display) { + return mScreenIndex; + } else return -1; +} diff --git a/Linphone/model/videoSource/VideoSourceDescriptorModel.hpp b/Linphone/model/videoSource/VideoSourceDescriptorModel.hpp new file mode 100644 index 000000000..2720f9b91 --- /dev/null +++ b/Linphone/model/videoSource/VideoSourceDescriptorModel.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIDEO_SOURCE_DESCRIPTOR_MODEL_H_ +#define VIDEO_SOURCE_DESCRIPTOR_MODEL_H_ + +// ============================================================================= +#include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" + +#include +#include +#include + +class VideoSourceDescriptorModel : public QObject, public AbstractObject { + Q_OBJECT + +public: + VideoSourceDescriptorModel(); + VideoSourceDescriptorModel(std::shared_ptr desc); + void setScreenSharingDisplay(int index); + void setScreenSharingWindow(void *window); // Get data from DesktopTools. + void *getScreenSharing() const; + + quint64 getWindowId() const; + void setWindowId(quint64 id); + + bool isScreenSharing() const; + LinphoneEnums::VideoSourceScreenSharingType getVideoSourceType() const; + int getScreenSharingIndex() const; + + std::shared_ptr mDesc; + int mScreenIndex = -1; + +signals: + void videoDescriptorChanged(); + +private: + DECLARE_ABSTRACT_OBJECT +}; +#endif diff --git a/Linphone/tool/CMakeLists.txt b/Linphone/tool/CMakeLists.txt index 6328ed747..dba7597f3 100644 --- a/Linphone/tool/CMakeLists.txt +++ b/Linphone/tool/CMakeLists.txt @@ -9,8 +9,30 @@ list(APPEND _LINPHONEAPP_SOURCES tool/thread/Thread.cpp tool/providers/AvatarProvider.cpp tool/providers/ImageProvider.cpp + tool/providers/ScreenProvider.cpp + + tool/native/DesktopTools.hpp ) +if (APPLE) + list(APPEND _LINPHONEAPP_SOURCES + tool/native/DesktopToolsMacOs.cpp + tool/native/DesktopToolsMacOsNative.mm + tool/native/screen-saver/ScreenSaverMacOs.m + tool/native/state-process/StateProcessMacOs.mm + ) +elseif (WIN32) + list(APPEND _LINPHONEAPP_SOURCES + tool/native/DesktopToolsWindows.cpp + ) +else () + list(APPEND _LINPHONEAPP_SOURCES + tool/native/DesktopToolsLinux.cpp + tool/native/screen-saver/ScreenSaverDBus.cpp + tool/native/screen-saver/ScreenSaverXdg.cpp + ) +endif () + set(_LINPHONEAPP_SOURCES ${_LINPHONEAPP_SOURCES} PARENT_SCOPE) diff --git a/Linphone/tool/LinphoneEnums.cpp b/Linphone/tool/LinphoneEnums.cpp index 9ad0b4fb0..c5dc62423 100644 --- a/Linphone/tool/LinphoneEnums.cpp +++ b/Linphone/tool/LinphoneEnums.cpp @@ -42,6 +42,7 @@ void LinphoneEnums::registerMetaTypes() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(LinphoneEnums::staticMetaObject, Constants::MainQmlUri, 1, 0, "LinphoneEnums", "Only enums"); } @@ -275,3 +276,12 @@ void LinphoneEnums::fromString(const QString &transportType, LinphoneEnums::Tran else if (transportType.toUpper() == QLatin1String("TLS")) *transport = TransportType::Tls; else *transport = TransportType::Dtls; } + +linphone::VideoSourceScreenSharingType +LinphoneEnums::toLinphone(const LinphoneEnums::VideoSourceScreenSharingType &type) { + return static_cast(type); +} +LinphoneEnums::VideoSourceScreenSharingType +LinphoneEnums::fromLinphone(const linphone::VideoSourceScreenSharingType &type) { + return static_cast(type); +} diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index dfca4e929..17583e117 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -308,6 +308,17 @@ Q_ENUM_NS(TransportType) linphone::TransportType toLinphone(const LinphoneEnums::TransportType &type); LinphoneEnums::TransportType fromLinphone(const linphone::TransportType &type); + +enum VideoSourceScreenSharingType { + VideoSourceScreenSharingTypeArea = int(linphone::VideoSourceScreenSharingType::Area), + VideoSourceScreenSharingTypeDisplay = int(linphone::VideoSourceScreenSharingType::Display), + VideoSourceScreenSharingTypeWindow = int(linphone::VideoSourceScreenSharingType::Window) +}; +Q_ENUM_NS(VideoSourceScreenSharingType) + +linphone::VideoSourceScreenSharingType toLinphone(const LinphoneEnums::VideoSourceScreenSharingType &type); +LinphoneEnums::VideoSourceScreenSharingType fromLinphone(const linphone::VideoSourceScreenSharingType &type); + QString toString(const LinphoneEnums::TransportType &type); void fromString(const QString &transportType, LinphoneEnums::TransportType *transport); } // namespace LinphoneEnums diff --git a/Linphone/tool/native/DesktopTools.hpp b/Linphone/tool/native/DesktopTools.hpp new file mode 100644 index 000000000..a370a2fd3 --- /dev/null +++ b/Linphone/tool/native/DesktopTools.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DESKTOP_TOOLS_H_ +#define DESKTOP_TOOLS_H_ + +#include + +#ifdef Q_OS_LINUX + #include "DesktopToolsLinux.hpp" +#elif defined(Q_OS_WIN) + #include "DesktopToolsWindows.hpp" +#else + #include "DesktopToolsMacOs.hpp" +#endif // ifdef Q_OS_LINUX + +// ============================================================================= + +#endif // DESKTOP_TOOLS_H_ diff --git a/Linphone/tool/native/DesktopToolsLinux.cpp b/Linphone/tool/native/DesktopToolsLinux.cpp new file mode 100644 index 000000000..28b296900 --- /dev/null +++ b/Linphone/tool/native/DesktopToolsLinux.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DesktopToolsLinux.hpp" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// ============================================================================= + +DesktopTools::~DesktopTools() { + setScreenSaverStatus(true); +} + +bool DesktopTools::getScreenSaverStatus() const { + return mScreenSaverStatus; +} + +void DesktopTools::setScreenSaverStatus(bool status) { + screenSaverDBus.setScreenSaverStatus(status); + screenSaverXdg.setScreenSaverStatus(status); + + bool newStatus = screenSaverDBus.getScreenSaverStatus() || screenSaverXdg.getScreenSaverStatus(); + if (newStatus != mScreenSaverStatus) { + mScreenSaverStatus = newStatus; + emit screenSaverStatusChanged(mScreenSaverStatus); + } +} + +#define ACTIVE_WINDOWS "_NET_CLIENT_LIST" +QList DesktopTools::getWindows() { +#ifdef ENABLE_SCREENSHARING + QList windowsMaps; + const char *displayStr = getenv("DISPLAY"); + if (displayStr == NULL) displayStr = ":0"; + Display *display = XOpenDisplay(displayStr); + if (display == NULL) { + qCritical() << "Can't open X display!"; + return windowsMaps; + } + + auto screen = DefaultScreen(display); + auto rootWindow = RootWindow(display, screen); + + // Algo 1 + Atom actualType; + int format; + unsigned long numItems; + unsigned long bytesAfter; + unsigned char *data = nullptr; + Window *list; + char *windowName; + Atom atom = XInternAtom(display, ACTIVE_WINDOWS, true); + int status = XGetWindowProperty(display, rootWindow, atom, 0L, (~0L), false, AnyPropertyType, &actualType, &format, + &numItems, &bytesAfter, &data); + list = (Window *)data; + if (status >= Success && numItems) { + for (size_t i = 0; i < numItems; ++i) { + status = XFetchName(display, list[i], &windowName); + if (status >= Success) { + QVariantMap windowMap; + windowMap["name"] = QString(windowName); + windowMap["windowId"] = (quint64)list[i]; + windowsMaps << windowMap; + XFree(windowName); + } + } + } + XFree(data); + + /* //Algo2 + Window root_return; + Window parent_return; + Window *children_return; + unsigned int nchildren_return; + + XQueryTree(display, rootWindow, &root_return, &parent_return, &children_return, &nchildren_return); + for (size_t i = 0; i < nchildren_return; ++i) { + XWindowAttributes attributes; + XGetWindowAttributes(display, children_return[i], &attributes); + // XTextProperty name; + // XGetWMName(display, children_return[i], &name); + char *name; + XFetchName(display, children_return[i], &name); + if (attributes.c_class == InputOutput && attributes.map_state == IsViewable) { + qDebug() << name << attributes.depth << attributes.root; + QVariantMap windowMap; + windowMap["name"] = QString(name); + windowMap["windowId"] = (quint64)children_return[i]; + windowsMaps << windowMap; + } + XFree(name); + } + + if (children_return) XFree(children_return); + qDebug() << "Found " << nchildren_return << "Windows"; + */ + + return windowsMaps; +#else + return QList(); +#endif +} + +QImage DesktopTools::takeScreenshot(void *window) { +#ifdef ENABLE_SCREENSHARING + Display *display = XOpenDisplay(NULL); + Window rootWindow = (Window)window; + XWindowAttributes attributes; + XGetWindowAttributes(display, rootWindow, &attributes); + + int width = attributes.width; + int height = attributes.height; + + XColor colors; + XImage *image; + QImage screenshot(width, height, QImage::Format_RGB32); + unsigned long red_mask; + unsigned long green_mask; + unsigned long blue_mask; + + image = XGetImage(display, rootWindow, 0, 0, width, height, AllPlanes, ZPixmap); + if (!image) return QImage(); + red_mask = image->red_mask; + green_mask = image->green_mask; + blue_mask = image->blue_mask; + + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + colors.pixel = XGetPixel(image, j, i); + screenshot.setPixel( + j, i, + qRgb((colors.pixel & red_mask) >> 16, (colors.pixel & green_mask) >> 8, colors.pixel & blue_mask)); + } + } + + XFree(image); + + return screenshot; +#else + Q_UNUSED(window) + return QImage(); +#endif +} + +QImage DesktopTools::getWindowIcon(void *window) { +#ifdef ENABLE_SCREENSHARING + Window rootWindow = (Window)window; + + QList windowsMaps; + const char *displayStr = getenv("DISPLAY"); + if (displayStr == NULL) displayStr = ":0"; + Display *display = XOpenDisplay(displayStr); + if (display == NULL) { + qCritical() << "Can't open X display!"; + return QImage(); + } + + auto screen = DefaultScreen(display); + + // Algo 1 + Atom actualType; + int format; + unsigned long numItems; + unsigned long bytesAfter; + unsigned char *data = nullptr; + Window *list; + char *windowName; + Atom atom = XInternAtom(display, "_NET_WM_ICON", true); + int status = XGetWindowProperty(display, rootWindow, atom, 0L, 1, 0, AnyPropertyType, &actualType, &format, + &numItems, &bytesAfter, &data); + if (!data) return QImage(); + int width = *(int *)data; + XFree(data); + XGetWindowProperty(display, rootWindow, atom, 1, 1, 0, AnyPropertyType, &actualType, &format, &numItems, + &bytesAfter, &data); + if (!data) return QImage(); + int height = *(int *)data; + XFree(data); + int size = width * height; + XGetWindowProperty(display, rootWindow, atom, 2, size, 0, AnyPropertyType, &actualType, &format, &numItems, + &bytesAfter, &data); + if (!data) return QImage(); + QImage icon(width, height, QImage::Format_ARGB32); + + unsigned int *imgData = new unsigned int[size]; + unsigned long *ul = (unsigned long *)data; + for (int i = 0; i < numItems; ++i) { + imgData[i] = (unsigned int)ul[i]; + } + unsigned char *argb = (unsigned char *)imgData; + // qDebug() << bytesAfter << " / " << numItems << height << "x" << width << " == " << height * width << " : " + // << height * width * 4; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + unsigned char a = argb[3]; + unsigned char r = argb[2] * a / 255; + unsigned char g = argb[1] * a / 255; + unsigned char b = argb[0] * a / 255; + icon.setPixel(x, y, qRgba(r, g, b, a)); + argb += 4; + } + } + XFree(data); + delete[] imgData; + return icon; +#else + Q_UNUSED(window) + return QImage(); +#endif +} + +uintptr_t DesktopTools::getDisplayIndex(void *screenSharing) { + Q_UNUSED(screenSharing) +#ifdef ENABLE_SCREENSHARING + return *(uintptr_t *)(&screenSharing); +#else + return 0; +#endif +} + +QRect DesktopTools::getWindowGeometry(void *screenSharing) { + Q_UNUSED(screenSharing) +#ifdef ENABLE_SCREENSHARING + const char *displayStr = getenv("DISPLAY"); + if (displayStr == NULL) displayStr = ":0"; + Display *display = XOpenDisplay(displayStr); + if (display == NULL) { + qCritical() << "Can't open X display!"; + return QRect(); + } + Window windowId = (Window)screenSharing; + XWindowAttributes attributes; + XGetWindowAttributes(display, windowId, &attributes); + return QRect(attributes.x, attributes.y, attributes.width, attributes.height); +#else + return QRect(); +#endif +} diff --git a/Linphone/tool/native/DesktopToolsLinux.hpp b/Linphone/tool/native/DesktopToolsLinux.hpp new file mode 100644 index 000000000..69cf89661 --- /dev/null +++ b/Linphone/tool/native/DesktopToolsLinux.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DESKTOP_TOOLS_LINUX_H_ +#define DESKTOP_TOOLS_LINUX_H_ + +#include "screen-saver/ScreenSaverDBus.hpp" +#include "screen-saver/ScreenSaverXdg.hpp" + +// ============================================================================= +class VideoSourceDescriptorModel; + +class DesktopTools : public QObject { + Q_OBJECT + + Q_PROPERTY( + bool screenSaverStatus READ getScreenSaverStatus WRITE setScreenSaverStatus NOTIFY screenSaverStatusChanged) + +public: + DesktopTools(QObject *parent = Q_NULLPTR) : QObject(parent) { + } + ~DesktopTools(); + + bool getScreenSaverStatus() const; + void setScreenSaverStatus(bool status); + + static void init() { + } + static void applicationStateChanged(Qt::ApplicationState){}; + static QList getWindows(); + static QImage takeScreenshot(void *window); + static QImage getWindowIcon(void *window); + + static void *getDisplay(uintptr_t screenIndex) { + return reinterpret_cast(screenIndex); + } + static uintptr_t getDisplayIndex(void *screenSharing); + static QRect getWindowGeometry(void *screenSharing); + +signals: + void screenSaverStatusChanged(bool status); + +private: + bool mScreenSaverStatus = true; + + ScreenSaverDBus screenSaverDBus; + ScreenSaverXdg screenSaverXdg; + + // X11 headers cannot be used in hpp. moc don't' compile. + void *mDisplay = nullptr; // Display + unsigned int mWindow = 0; // Window +}; + +#endif // DESKTOP_TOOLS_LINUX_H_ diff --git a/Linphone/tool/native/DesktopToolsMacOs.cpp b/Linphone/tool/native/DesktopToolsMacOs.cpp new file mode 100644 index 000000000..25365e8fd --- /dev/null +++ b/Linphone/tool/native/DesktopToolsMacOs.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DesktopToolsMacOs.hpp" + +// ============================================================================= + +extern "C" bool enableScreenSaverMacOs (); +extern "C" bool disableScreenSaverMacOs (); + +DesktopTools::DesktopTools (QObject *parent) : QObject(parent) {} + +DesktopTools::~DesktopTools () { + setScreenSaverStatus(true); +} + +bool DesktopTools::getScreenSaverStatus () const { + return mScreenSaverStatus; +} + +void DesktopTools::setScreenSaverStatus (bool status) { + if (status != mScreenSaverStatus && (status ? enableScreenSaverMacOs() : disableScreenSaverMacOs())) { + mScreenSaverStatus = status; + emit screenSaverStatusChanged(mScreenSaverStatus); + } +} diff --git a/Linphone/tool/native/DesktopToolsMacOs.hpp b/Linphone/tool/native/DesktopToolsMacOs.hpp new file mode 100644 index 000000000..8bb7b4d22 --- /dev/null +++ b/Linphone/tool/native/DesktopToolsMacOs.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DESKTOP_TOOLS_MAC_OS_H_ +#define DESKTOP_TOOLS_MAC_OS_H_ + +#include +#include +#include +#include +// ============================================================================= +class VideoSourceDescriptorModel; + +class DesktopTools : public QObject { + Q_OBJECT; + + Q_PROPERTY( + bool screenSaverStatus READ getScreenSaverStatus WRITE setScreenSaverStatus NOTIFY screenSaverStatusChanged); + +public: + DesktopTools(QObject *parent = Q_NULLPTR); + ~DesktopTools(); + + bool getScreenSaverStatus() const; + void setScreenSaverStatus(bool status); + + static void init(); // Do first initialization + static void applicationStateChanged(Qt::ApplicationState currentState); + + static QList getWindows(); + static QImage takeScreenshot(void *window); + static QImage getWindowIcon(void *window); + + static void *getDisplay(int screenIndex); + static int getDisplayIndex(void *screenSharing); + static QRect getWindowGeometry(void *screenSharing); + +signals: + void screenSaverStatusChanged(bool status); + +private: + bool mScreenSaverStatus = true; +}; + +#endif // DESKTOP_TOOLS_MAC_OS_H_ diff --git a/Linphone/tool/native/DesktopToolsMacOsNative.mm b/Linphone/tool/native/DesktopToolsMacOsNative.mm new file mode 100644 index 000000000..0d5e65185 --- /dev/null +++ b/Linphone/tool/native/DesktopToolsMacOsNative.mm @@ -0,0 +1,228 @@ +#include "DesktopToolsMacOs.hpp" +#include "config.h" + +#import +#import +#include +#include +#include + +void DesktopTools::init(){ +// Request permissions + if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized) { + qDebug() << "Requesting Video permission"; + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL) {}]; + } + if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] != AVAuthorizationStatusAuthorized){ + qDebug() << "Requesting Audio permission"; + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL) {}]; + } +} + +bool isWindowMinimized(CGWindowID id) { + CFArrayRef ids = CFArrayCreate(NULL, reinterpret_cast(&id), 1, NULL); + CFArrayRef descriptions = CGWindowListCreateDescriptionFromArray(ids); + bool minimized = false; + + if (descriptions && CFArrayGetCount(descriptions)) { + CFDictionaryRef window = reinterpret_cast(CFArrayGetValueAtIndex(descriptions, 0)); + minimized = ! reinterpret_cast(CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + } + + CFRelease(descriptions); + CFRelease(ids); + + return minimized; +} + +QList DesktopTools::getWindows() { + QList windows; + bool haveAccess = CGPreflightScreenCaptureAccess(); + //Requests event listening access if absent, potentially prompting + if(!haveAccess) haveAccess = CGRequestScreenCaptureAccess(); + + CFArrayRef infos = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID); + for(int windowIndex = 0 ; windowIndex < CFArrayGetCount(infos) ; ++windowIndex) { + CFDictionaryRef description = (CFDictionaryRef)CFArrayGetValueAtIndex ((CFArrayRef)infos, windowIndex); + if(CFDictionaryContainsKey(description, kCGWindowNumber) + && CFDictionaryContainsKey(description, kCGWindowLayer) + && CFDictionaryContainsKey(description, kCGWindowName) + ) { + CFNumberRef idRef = (CFNumberRef)CFDictionaryGetValue (description, kCGWindowNumber); + CFNumberRef layerRef = (CFNumberRef)CFDictionaryGetValue (description, kCGWindowLayer); + CFStringRef titleRef = (CFStringRef)CFDictionaryGetValue (description, kCGWindowName); + CGWindowID id=0, layer=0; + + if (!CFNumberGetValue(idRef, kCFNumberIntType, &id) || !CFNumberGetValue(layerRef, kCFNumberIntType, &layer)) { + continue; + } + + QVariantMap window; + if(CFDictionaryContainsKey(description, kCGWindowName)) { + window["name"] = QString::fromCFString(titleRef); + } + if( window["name"] == "") continue; + window["windowId"] = id; + + // Skip layer != 0 like menu or dock + if(layer != 0 || isWindowMinimized(id)) + continue; + // Remove System status indicator from the list as they are on layer 0 + CFStringRef ownerName = reinterpret_cast(CFDictionaryGetValue(description, kCGWindowOwnerName)); + if (titleRef && CFEqual(titleRef, CFSTR("StatusIndicator")) && ownerName && CFEqual(ownerName, CFSTR("Window Server"))) { + continue; + } + windows << window; + } + } + if(infos) CFRelease(infos); + return windows; +} +CGBitmapInfo CGBitmapInfoForQImage(const QImage &image) { + CGBitmapInfo bitmapInfo = kCGImageAlphaNone; + + switch (image.format()) { + case QImage::Format_ARGB32: + bitmapInfo = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; + break; + case QImage::Format_RGB32: + bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; + break; + case QImage::Format_RGBA8888_Premultiplied: + bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big; + break; + case QImage::Format_RGBA8888: + bitmapInfo = kCGImageAlphaLast | kCGBitmapByteOrder32Big; + break; + case QImage::Format_RGBX8888: + bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big; + break; + case QImage::Format_ARGB32_Premultiplied: + bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; + break; + default: + break; + } + + return bitmapInfo; +} + +QImage CGImageToQImage(CGImageRef cgImage) { + const size_t width = CGImageGetWidth(cgImage); + const size_t height = CGImageGetHeight(cgImage); + QImage image(width, height, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + CGContextRef context = CGBitmapContextCreate((void *)image.bits(), image.width(), image.height(), 8, + image.bytesPerLine(), colorSpace, CGBitmapInfoForQImage(image)); + + // Scale the context so that painting happens in device-independent pixels + const qreal devicePixelRatio = image.devicePixelRatio(); + CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio); + + CGRect rect = CGRectMake(0, 0, width, height); + CGContextDrawImage(context, rect, cgImage); + + if(colorSpace) CFRelease(colorSpace); + if(context) CFRelease(context); + + return image; +} + +QImage DesktopTools::getWindowIcon(void *window) { + CGWindowID windowId = *(CGWindowID*)&window; + pid_t pid=0; + + CFArrayRef infos = CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowId); + NSString *bundleIdentifier = @""; + if (infos && CFArrayGetCount(infos)) { + CFDictionaryRef w = reinterpret_cast(CFArrayGetValueAtIndex(infos, 0)); + CFNumberRef pidRef = reinterpret_cast(CFDictionaryGetValue(w, kCGWindowOwnerPID)); + CFNumberGetValue(pidRef, kCFNumberIntType, &pid) ; + bundleIdentifier = [[NSRunningApplication runningApplicationWithProcessIdentifier:pid] bundleIdentifier]; + } + + if(infos) CFRelease(infos); + + if(bundleIdentifier) { + NSURL* url = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:bundleIdentifier]; + NSString * noScheme = [[url absoluteString] substringFromIndex:7]; + NSImage *nsImage = [[NSWorkspace sharedWorkspace] iconForFile:noScheme]; + + NSRect rect = NSMakeRect(0, 0, nsImage.size.width, nsImage.size.height); + CGImageRef cgImage = [nsImage CGImageForProposedRect:&rect context:NULL hints:nil]; + + return CGImageToQImage(cgImage); + }else { + qWarning() << "Screensharing : Bundle identifier is null for CGID=" << windowId << " pid="<< pid; + return QImage(); + } +} + +QImage DesktopTools::takeScreenshot(void *window) { + + CGWindowID windowId = *(CGWindowID*)&window; + CGImageRef capture = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowId, kCGWindowImageBoundsIgnoreFraming); + return CGImageToQImage(capture); +} + + +void *DesktopTools::getDisplay(int screenIndex){ + Q_UNUSED(screenIndex) +#ifdef ENABLE_SCREENSHARING + CGDirectDisplayID displays[screenIndex+1]; + CGDisplayCount displayCount; + CGGetOnlineDisplayList(screenIndex+1, displays, &displayCount); + CGDirectDisplayID display = 0; + if(displayCount > screenIndex) + display = displays[screenIndex]; + return reinterpret_cast(display); +#else + return NULL; +#endif +} + +int DesktopTools::getDisplayIndex(void* screenSharing){ + Q_UNUSED(screenSharing) +#ifdef ENABLE_SCREENSHARING + CGDirectDisplayID displayId = *(CGDirectDisplayID*)&screenSharing; + int maxDisplayCount = 10; + CGDisplayCount displayCount; + do { + CGDirectDisplayID displays[maxDisplayCount]; + CGGetOnlineDisplayList(maxDisplayCount, displays, &displayCount); + for(int i = 0 ; i < displayCount ; ++i) + if( displays[i] == displayId) { + return i; + } + maxDisplayCount *= 2; + }while(displayCount == maxDisplayCount/2); +#endif + return 0; +} + +QRect DesktopTools::getWindowGeometry(void* screenSharing) { + Q_UNUSED(screenSharing) + QRect result; +#ifdef ENABLE_SCREENSHARING + CGWindowID windowId = *(CGWindowID*)&screenSharing; + CFArrayRef descriptions = CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowId); + if(CFArrayGetCount(descriptions) > 0) { + CFDictionaryRef description = (CFDictionaryRef)CFArrayGetValueAtIndex ((CFArrayRef)descriptions, 0); + if(CFDictionaryContainsKey(description, kCGWindowBounds)) { + CFDictionaryRef bounds = (CFDictionaryRef)CFDictionaryGetValue (description, kCGWindowBounds); + if(bounds) { + CGRect windowRect; + CGRectMakeWithDictionaryRepresentation(bounds, &windowRect); + result = QRect(windowRect.origin.x, windowRect.origin.y, windowRect.size.width, windowRect.size.height); + }else + qWarning() << "Bounds found be cannot be parsed for Window ID : " << windowId; + }else + qWarning() << "No bounds specified in Apple description for Window ID : " << windowId; + }else + qWarning() << "No description found for Window ID : " << windowId; + if(descriptions) CFRelease(descriptions); +#endif + return result; +} diff --git a/Linphone/tool/native/DesktopToolsWindows.cpp b/Linphone/tool/native/DesktopToolsWindows.cpp new file mode 100644 index 000000000..1dba3181d --- /dev/null +++ b/Linphone/tool/native/DesktopToolsWindows.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DesktopToolsWindows.hpp" + +#include +#include +#include +#include +#include +#include +#include +// ============================================================================= + +DesktopTools::DesktopTools(QObject *parent) : QObject(parent) { +} + +DesktopTools::~DesktopTools() { + setScreenSaverStatus(true); +} + +bool DesktopTools::getScreenSaverStatus() const { + return mScreenSaverStatus; +} + +void DesktopTools::setScreenSaverStatus(bool status) { + if (status == mScreenSaverStatus) return; + + if (status) SetThreadExecutionState(ES_CONTINUOUS); + else SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); + + mScreenSaverStatus = status; + emit screenSaverStatusChanged(status); +} + +//----------- Get Windows +void getWindowMap(HWND hwnd, LPARAM lParam) { + const DWORD TITLE_SIZE = 1024; + WCHAR windowTitle[TITLE_SIZE]; + + GetWindowTextW(hwnd, windowTitle, TITLE_SIZE); + std::wstring title(&windowTitle[0]); + int length = ::GetWindowTextLength(hwnd); + if (!IsWindowVisible(hwnd) || length == 0) { + return; + } + QList &windowsMap = *reinterpret_cast *>(lParam); + QVariantMap windowMap; + windowMap["name"] = QString::fromStdWString(title); + windowMap["windowId"] = (quint64)hwnd; + windowsMap << windowMap; +} + +BOOL CALLBACK getChildWindowsCb(HWND hwnd, LPARAM lParam) { + getWindowMap(hwnd, lParam); + return TRUE; +} + +BOOL CALLBACK getWindowsCb(HWND hwnd, LPARAM lParam) { + getWindowMap(hwnd, lParam); + // EnumChildWindows(hwnd, getChildWindowsCb, lParam); + return TRUE; +} + +QList DesktopTools::getWindows() { +#ifdef ENABLE_SCREENSHARING + QList windowsMap; + EnumWindows(getWindowsCb, reinterpret_cast(&windowsMap)); + return windowsMap; +#else + return QList(); +#endif +} + +//----------------------------------------------------------- + +#ifndef GCL_HICON +#define GCL_HICON -14 +#endif + +QImage DesktopTools::getWindowIcon(void *window) { +#ifdef ENABLE_SCREENSHARING +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QPixmap pixmap = QtWin::fromHICON(icon); + return pixmap.toImage(); +#else + return QImage::fromHICON(icon); +#endif + +#else + return QImage(); +#endif +} + +QImage DesktopTools::takeScreenshot(void *window) { + QImage image; +#ifdef ENABLE_SCREENSHARING + RECT rect = {0}; + + if (!GetWindowRect((HWND)window, &rect)) { + qCritical() << "[DesktopTools] Cannot get window size"; + return image; + } + HDC hDC = GetDC((HWND)window); + if (hDC == NULL) { + qCritical() << "[DesktopTools] GetDC failed."; + return image; + } + HDC hTargetDC = CreateCompatibleDC(hDC); + if (hTargetDC == NULL) { + ReleaseDC((HWND)window, hDC); + qCritical() << "[DesktopTools] CreateCompatibleDC failed."; + return image; + } + HBITMAP hBitmap = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top); + if (hBitmap == NULL) { + ReleaseDC((HWND)window, hDC); + ReleaseDC((HWND)window, hTargetDC); + qCritical() << "[DesktopTools] CreateCompatibleBitmap failed."; + return image; + } + if (!SelectObject(hTargetDC, hBitmap)) { + DeleteObject(hBitmap); + ReleaseDC((HWND)window, hDC); + ReleaseDC((HWND)window, hTargetDC); + qCritical() << "[DesktopTools] SelectObject failed."; + return image; + } + + if (!PrintWindow((HWND)window, hTargetDC, PW_RENDERFULLCONTENT)) { + DeleteObject(hBitmap); + ReleaseDC((HWND)window, hDC); + ReleaseDC((HWND)window, hTargetDC); + qCritical() << "[DesktopTools] PrintWindow failed."; + return image; + } + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QPixmap pixmap = QtWin::fromHBITMAP(hBitmap); + image = pixmap.toImage(); +#else + image = QImage::fromHBITMAP(hBitmap); +#endif + DeleteObject(hBitmap); + ReleaseDC((HWND)window, hDC); + ReleaseDC((HWND)window, hTargetDC); +#endif + return image; +} + +uintptr_t DesktopTools::getDisplayIndex(void *screenSharing) { + Q_UNUSED(screenSharing) +#ifdef ENABLE_SCREENSHARING + return *(uintptr_t *)(&screenSharing); +#else + return NULL; +#endif +} + +QRect DesktopTools::getWindowGeometry(void *screenSharing) { + Q_UNUSED(screenSharing) + QRect result; +#ifdef ENABLE_SCREENSHARING + HWND windowId = *(HWND *)&screenSharing; + RECT area; + if (S_OK == DwmGetWindowAttribute(windowId, DWMWA_EXTENDED_FRAME_BOUNDS, &area, sizeof(RECT))) { + result = QRect(area.left + 1, area.top, area.right - area.left, area.bottom - area.top); // +1 for border + } else qWarning() << "Cannot get attributes from HWND: " << windowId; +#endif + return result; +} diff --git a/Linphone/tool/native/DesktopToolsWindows.hpp b/Linphone/tool/native/DesktopToolsWindows.hpp new file mode 100644 index 000000000..bae9909f8 --- /dev/null +++ b/Linphone/tool/native/DesktopToolsWindows.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DESKTOP_TOOLS_WINDOWS_H_ +#define DESKTOP_TOOLS_WINDOWS_H_ + +#include +#include +#include +#include + +class VideoSourceDescriptorModel; +// ============================================================================= + +class DesktopTools : public QObject { + Q_OBJECT + + Q_PROPERTY( + bool screenSaverStatus READ getScreenSaverStatus WRITE setScreenSaverStatus NOTIFY screenSaverStatusChanged) + +public: + DesktopTools(QObject *parent = Q_NULLPTR); + ~DesktopTools(); + + bool getScreenSaverStatus() const; + void setScreenSaverStatus(bool status); + + static void init() { + } + static void applicationStateChanged(Qt::ApplicationState){}; + + static QList getWindows(); + static QImage takeScreenshot(void *window); + static QImage getWindowIcon(void *window); + + static void *getDisplay(uintptr_t screenIndex) { + return reinterpret_cast(screenIndex); + } + static uintptr_t getDisplayIndex(void *screenSharing); + static QRect getWindowGeometry(void *screenSharing); + HWND mWindowId = 0; // Window + VideoSourceDescriptorModel *mVideoSourceDescriptorModel = nullptr; + +signals: + void screenSaverStatusChanged(bool status); + void windowIdSelectionStarted(); + void windowIdSelectionEnded(); + +private: + bool mScreenSaverStatus = true; +}; + +#endif // DESKTOP_TOOLS_WINDOWS_H_ diff --git a/Linphone/tool/native/screen-saver/ScreenSaverDBus.cpp b/Linphone/tool/native/screen-saver/ScreenSaverDBus.cpp new file mode 100644 index 000000000..3421f80e8 --- /dev/null +++ b/Linphone/tool/native/screen-saver/ScreenSaverDBus.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "ScreenSaverDBus.hpp" + +// ============================================================================= + +namespace { + constexpr char ServiceName[] = "org.freedesktop.ScreenSaver"; + constexpr char ServicePath[] = "/ScreenSaver"; +} + +ScreenSaverDBus::ScreenSaverDBus (QObject *parent) : QObject(parent), mBus(ServiceName, ServicePath, ServiceName) {} + +ScreenSaverDBus::~ScreenSaverDBus () { + setScreenSaverStatus(true); +} + +bool ScreenSaverDBus::getScreenSaverStatus () const { + return mScreenSaverStatus; +} + +void ScreenSaverDBus::setScreenSaverStatus (bool status) { + if (status == mScreenSaverStatus) + return; + + if (status) { + QDBusMessage reply(mBus.call("UnInhibit", mToken)); + if (reply.type() == QDBusMessage::ErrorMessage) { + qWarning() << QStringLiteral("Uninhibit screen saver failed: `%1: %2`.") + .arg(reply.errorName()).arg(reply.errorMessage()); + return; + } else + qInfo("Uninhibit screen saver."); + + mToken = uint32_t(reply.arguments().first().toULongLong()); + mScreenSaverStatus = status; + emit screenSaverStatusChanged(mScreenSaverStatus); + return; + } + + QDBusMessage reply(mBus.call("Inhibit", QCoreApplication::applicationName(), "Inhibit asked for video stream")); + if (reply.type() == QDBusMessage::ErrorMessage) { + if (reply.errorName() != QLatin1String("org.freedesktop.DBus.Error.ServiceUnknown")) + qWarning() << QStringLiteral("Inhibit screen saver failed: `%1: %2`.") + .arg(reply.errorName()).arg(reply.errorMessage()); + return; + } else + qInfo("Inhibit screen saver."); + + mScreenSaverStatus = status; + emit screenSaverStatusChanged(mScreenSaverStatus); +} diff --git a/Linphone/tool/native/screen-saver/ScreenSaverDBus.hpp b/Linphone/tool/native/screen-saver/ScreenSaverDBus.hpp new file mode 100644 index 000000000..b2615205f --- /dev/null +++ b/Linphone/tool/native/screen-saver/ScreenSaverDBus.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREEN_SAVER_DBUS_H_ +#define SCREEN_SAVER_DBUS_H_ + +#include + +// ============================================================================= + +class QDBusPendingCallWatcher; + +class ScreenSaverDBus : public QObject { + Q_OBJECT; + +public: + ScreenSaverDBus (QObject *parent = Q_NULLPTR); + ~ScreenSaverDBus (); + + bool getScreenSaverStatus () const; + void setScreenSaverStatus (bool status); + +signals: + void screenSaverStatusChanged (bool status); + +private: + bool mScreenSaverStatus = true; + + QDBusInterface mBus; + uint32_t mToken; +}; + +#endif // SCREEN_SAVER_DBUS_H_ diff --git a/Linphone/tool/native/screen-saver/ScreenSaverMacOs.m b/Linphone/tool/native/screen-saver/ScreenSaverMacOs.m new file mode 100644 index 000000000..5704fe6bc --- /dev/null +++ b/Linphone/tool/native/screen-saver/ScreenSaverMacOs.m @@ -0,0 +1,49 @@ +/* + * ScreenSaverMacOS.m + * Copyright (C) 2017-2018 Belledonne Communications, Grenoble, France + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Created on: August 3, 2018 + * Author: Ronan Abhamon + */ + +#import + +// ============================================================================= + +static bool ScreenSaverEnabled = true; +static IOPMAssertionID AssertionID; + +bool enableScreenSaverMacOs () { + if (ScreenSaverEnabled) + return true; + + ScreenSaverEnabled = IOPMAssertionRelease(AssertionID) == kIOReturnSuccess; + return ScreenSaverEnabled; +} + +bool disableScreenSaverMacOs () { + if (!ScreenSaverEnabled) + return true; + + ScreenSaverEnabled = IOPMAssertionCreateWithName( + kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, + CFSTR("Inhibit asked for video stream"), + &AssertionID + ) != kIOReturnSuccess; + return !ScreenSaverEnabled; +} diff --git a/Linphone/tool/native/screen-saver/ScreenSaverXdg.cpp b/Linphone/tool/native/screen-saver/ScreenSaverXdg.cpp new file mode 100644 index 000000000..81a2af5b0 --- /dev/null +++ b/Linphone/tool/native/screen-saver/ScreenSaverXdg.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "ScreenSaverXdg.hpp" + +// ============================================================================= + +namespace { + constexpr char Program[] = "xdg-screensaver"; + const QStringList Arguments{"reset"}; + + constexpr int Interval = 30000; +} + +ScreenSaverXdg::ScreenSaverXdg (QObject *parent) : QObject(parent) { + mTimer.setInterval(Interval); + QObject::connect(&mTimer, &QTimer::timeout, []() { + // Legacy for systems without DBus screensaver. + QProcess::startDetached(Program, Arguments); + }); +} + +bool ScreenSaverXdg::getScreenSaverStatus () const { + return !mTimer.isActive(); +} + +void ScreenSaverXdg::setScreenSaverStatus (bool status) { + if (status == !mTimer.isActive()) + return; + + if (status) + mTimer.stop(); + else + mTimer.start(); + + emit screenSaverStatusChanged(status); +} diff --git a/Linphone/tool/native/screen-saver/ScreenSaverXdg.hpp b/Linphone/tool/native/screen-saver/ScreenSaverXdg.hpp new file mode 100644 index 000000000..b84dfcadf --- /dev/null +++ b/Linphone/tool/native/screen-saver/ScreenSaverXdg.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREEN_SAVER_XDG_H_ +#define SCREEN_SAVER_XDG_H_ + +#include + +// ============================================================================= + +class ScreenSaverXdg : public QObject { + Q_OBJECT; + +public: + ScreenSaverXdg (QObject *parent = Q_NULLPTR); + + bool getScreenSaverStatus () const; + void setScreenSaverStatus (bool status); + +signals: + void screenSaverStatusChanged (bool status); + +private: + QTimer mTimer; +}; + +#endif // SCREEN_SAVER_XDG_H_ diff --git a/Linphone/tool/native/state-process/StateProcessMacOs.mm b/Linphone/tool/native/state-process/StateProcessMacOs.mm new file mode 100644 index 000000000..cb536ba5a --- /dev/null +++ b/Linphone/tool/native/state-process/StateProcessMacOs.mm @@ -0,0 +1,20 @@ +#include "../DesktopToolsMacOs.hpp" +#import +#import + +// Store a unique global instance of Activity to avoid App Nap of MacOs +static id g_backgroundActivity =0; + +void DesktopTools::applicationStateChanged(Qt::ApplicationState p_currentState) +{ + if( p_currentState == Qt::ApplicationActive && g_backgroundActivity != 0 ) + {// Entering Foreground + [[NSProcessInfo processInfo] endActivity:g_backgroundActivity]; + [g_backgroundActivity release]; + g_backgroundActivity = 0; + }else if( g_backgroundActivity == 0 ) + {// Doesn't begin activity if it is already started + g_backgroundActivity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep reason:@"Linphone : Continue to receive requests while in Background"]; + [g_backgroundActivity retain]; + } +} diff --git a/Linphone/tool/providers/ScreenProvider.cpp b/Linphone/tool/providers/ScreenProvider.cpp new file mode 100644 index 000000000..4e8d56611 --- /dev/null +++ b/Linphone/tool/providers/ScreenProvider.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenProvider.hpp" +#include "tool/native/DesktopTools.hpp" +#include +#include +#include +#include + +// ============================================================================= + +const QString ScreenProvider::ProviderId = "screen"; + +ScreenProvider::ScreenProvider() + : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) { +} + +QImage ScreenProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + if (!requestedSize.isNull()) { + int index = id.toInt(); + auto screens = QGuiApplication::screens(); + if (index >= 0 && index < screens.size()) { + auto screen = screens[index]; + auto geometry = screen->geometry(); +#if __APPLE__ + auto image = screen->grabWindow(0, geometry.x(), geometry.y(), geometry.width(), geometry.height()); +#else + auto image = screen->grabWindow(0, 0, 0, geometry.width(), geometry.height()); +#endif + if (requestedSize.isValid() && requestedSize.height() > 0 && requestedSize.width() > 0 && !image.isNull()) { + image = image.scaled(requestedSize, Qt::KeepAspectRatio, + Qt::FastTransformation); // Qt::SmoothTransformation); + } + *size = image.size(); + qDebug() << "Screen(" << index << ") = " << screen->geometry() << " VG:" << screen->virtualGeometry() + << " / " << screen->size() << " VS:" << screen->virtualSize() + << " DeviceRatio: " << screen->devicePixelRatio() << ", " << *size << " / " << requestedSize; + return image.toImage(); + } + } + QImage image(10, 10, QImage::Format_Indexed8); + image.fill(Qt::gray); + *size = image.size(); + return image; +} + +// ============================================================================= + +const QString WindowProvider::ProviderId = "window"; + +WindowProvider::WindowProvider() + : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) { +} + +// Note: Using WId don't work with Window/Mac (For Mac, NSView cannot be used while we are working with CGWindowID) +// Also WId only work for the current application. +QImage WindowProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + if (!requestedSize.isNull()) { + auto winId = id.toLongLong(); + if (winId > 0) { + auto image = DesktopTools::takeScreenshot((void *)winId); + if (requestedSize.isValid() && !image.isNull()) + image = image.scaled(requestedSize, Qt::KeepAspectRatio, + Qt::FastTransformation); // Qt::SmoothTransformation); + *size = image.size(); + return image; + } + } + QImage image(10, 10, QImage::Format_Indexed8); + image.fill(Qt::gray); + *size = image.size(); + return image; +} + +// ============================================================================= + +const QString WindowIconProvider::ProviderId = "window_icon"; + +WindowIconProvider::WindowIconProvider() + : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) { +} + +// Note: Using WId don't work with Window/Mac (For Mac, NSView cannot be used while we are working with CGWindowID) +// Also WId only work for the current application. +QImage WindowIconProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + if (!requestedSize.isNull()) { + auto winId = id.toLongLong(); + if (winId > 0) { + auto image = DesktopTools::getWindowIcon((void *)winId); + if (requestedSize.isValid() && !image.isNull()) + image = image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + *size = image.size(); + return image; + } + } + QImage image(10, 10, QImage::Format_Indexed8); + image.fill(Qt::gray); + *size = image.size(); + return image; +} diff --git a/Linphone/tool/providers/ScreenProvider.hpp b/Linphone/tool/providers/ScreenProvider.hpp new file mode 100644 index 000000000..c5027bee7 --- /dev/null +++ b/Linphone/tool/providers/ScreenProvider.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREEN_PROVIDER_H_ +#define SCREEN_PROVIDER_H_ + +#include + +// ============================================================================= + +class ScreenProvider : public QQuickImageProvider { +public: + ScreenProvider(); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + + static const QString ProviderId; +}; + +class WindowProvider : public QQuickImageProvider { +public: + WindowProvider(); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + + static const QString ProviderId; +}; + +class WindowIconProvider : public QQuickImageProvider { +public: + WindowIconProvider(); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + + static const QString ProviderId; +}; + +#endif diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index 55a26a5bc..8a82ef56a 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -719,11 +719,11 @@ AppWindow { Control.StackView.onActivated: rightPanel.headerTitleText = qsTr("Paramètres") InCallSettingsPanel { anchors.fill: parent - call: mainWindow.call anchors.topMargin: 16 * DefaultStyle.dp anchors.bottomMargin: 16 * DefaultStyle.dp anchors.leftMargin: 17 * DefaultStyle.dp anchors.rightMargin: 17 * DefaultStyle.dp + call: mainWindow.call } } } @@ -732,6 +732,7 @@ AppWindow { Item { Control.StackView.onActivated: rightPanel.headerTitleText = qsTr("Partage de votre écran") ScreencastPanel { + anchors.fill: parent anchors.topMargin: 16 * DefaultStyle.dp anchors.bottomMargin: 16 * DefaultStyle.dp anchors.leftMargin: 17 * DefaultStyle.dp diff --git a/Linphone/view/Item/Call/Menu/ScreencastPanel.qml b/Linphone/view/Item/Call/Menu/ScreencastPanel.qml index 68afba49c..729b72d79 100644 --- a/Linphone/view/Item/Call/Menu/ScreencastPanel.qml +++ b/Linphone/view/Item/Call/Menu/ScreencastPanel.qml @@ -6,10 +6,16 @@ import Linphone import UtilsCpp 1.0 ColumnLayout { + id: mainItem property CallGui call + property ConferenceGui conference: call.core.conference + property var desc: call.core.videoSourceDescriptor + property bool isLocalScreenSharing : conference?.core.isLocalScreenSharing || false + property bool screenSharingAvailable: !!conference && (!conference.core.isScreenSharingEnabled || isLocalScreenSharing) spacing: 12 * DefaultStyle.dp + onIsLocalScreenSharingChanged: {if(isLocalScreenSharing) mainItem.call.core.videoSourceDescriptor = mainItem.desc } Text { Layout.fillWidth: true text: qsTr("Veuillez choisir l’écran ou la fenêtre que vous souihaitez partager au autres participants") @@ -28,12 +34,16 @@ ColumnLayout { property var screenSource property int screenIndex property bool selected: false + property bool displayScreen: true + property int horizontalMargin: 0 leftPadding: 18 * DefaultStyle.dp rightPadding: 18 * DefaultStyle.dp topPadding: 13 * DefaultStyle.dp bottomPadding: 13 * DefaultStyle.dp background: Rectangle { anchors.fill: parent + anchors.leftMargin: screenPreview.horizontalMargin + anchors.rightMargin: screenPreview.horizontalMargin color: screenPreview.selected ? DefaultStyle.main2_100 : DefaultStyle.grey_0 border.width: 2 * DefaultStyle.dp border.color: screenPreview.selected ? DefaultStyle.main2_400 : DefaultStyle.main2_200 @@ -46,43 +56,106 @@ ColumnLayout { } } contentItem: ColumnLayout { - spacing: 0 - // TODO : replace this by screen preview - Rectangle { + spacing: 0 + Item{ Layout.fillWidth: true - Layout.preferredHeight: 170 * DefaultStyle.dp + Layout.fillHeight: true + Image { + anchors.centerIn: parent + //Layout.preferredHeight: 170 * DefaultStyle.dp + source: $modelData?.windowId ? "image://window/"+ $modelData.windowId : "image://screen/"+ $modelData.screenIndex + sourceSize.width: parent.width + sourceSize.height: parent.height + cache: false + } } - Text { - text: qsTr("Ecran %1").arg(screenIndex) - horizontalAlignment: Text.AlignHCenter - font.pixelSize: 14 * DefaultStyle.dp - Layout.fillWidth: true + RowLayout{ + Layout.topMargin: 6 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Image{ + Layout.preferredHeight: 15 * DefaultStyle.dp + Layout.preferredWidth: 15 * DefaultStyle.dp + visible: !!$modelData?.windowId + source: visible ? "image://window_icon/"+ $modelData.windowId : '' + sourceSize.width: width + sourceSize.height: height + cache: false + } + Text { + Layout.fillWidth: true + text: !!$modelData?.windowId ? $modelData.name : qsTr("Ecran %1").arg(screenIndex+1) + horizontalAlignment: Text.AlignHCenter + font.pixelSize: (displayScreen ? 14 : 10)* DefaultStyle.dp + elide: Text.ElideRight + maximumLineCount: 1 + } } } } StackLayout { currentIndex: bar.currentIndex - ColumnLayout { + ListView{ id: screensLayout - spacing: 0 - property int selectedIndex - Repeater { - model: 2 //TODO : screensModel - ScreenPreviewLayout { - Layout.fillWidth: true - screenIndex: index - onClicked: screensLayout.selectedIndex = index - selected: screensLayout.selectedIndex === index - } + spacing: 16 * DefaultStyle.dp + clip: true + //property int selectedIndex + model: ScreenProxy{ + id: screensList + mode: ScreenList.SCREENS } + onVisibleChanged: if(visible) screensList.update() + delegate: ScreenPreviewLayout { + horizontalMargin: (28 - 20 ) * DefaultStyle.dp // 20 coming from CallsWindow panel + width: screensLayout.width + height: 219 * DefaultStyle.dp + screenIndex: index + onClicked: {//screensLayout.selectedIndex = index + mainItem.desc.core.screenSharingIndex = index + if( mainItem.conference.core.isLocalScreenSharing) + mainItem.call.core.videoSourceDescriptor = mainItem.desc + } + selected: //screensLayout.selectedIndex === index + mainItem.desc.core.screenSharingIndex === index + } } - Text { - Layout.topMargin: 30 * DefaultStyle.dp - font.pixelSize: 20 * DefaultStyle.dp - text: qsTr("Cliquez sur la fenêtre à partager") + + GridView{ + id: windowsLayout + //property int selectedIndex + model: ScreenProxy{ + id: windowsList + mode: ScreenList.WINDOWS + } + onVisibleChanged: if(visible) windowsList.update() + cellWidth: width / 2 + cellHeight: (112 + 15) * DefaultStyle.dp + clip: true + delegate: Item{ + width: windowsLayout.cellWidth + height: windowsLayout.cellHeight + ScreenPreviewLayout { + anchors.fill: parent + anchors.margins: 7 * DefaultStyle.dp + displayScreen: false + screenIndex: index + onClicked: { + mainItem.desc.core.windowId = $modelData.windowId + if( mainItem.conference.core.isLocalScreenSharing) + mainItem.call.core.videoSourceDescriptor = mainItem.desc + } + selected: mainItem.desc.core.windowId == $modelData.windowId + + //onClicked: screensLayout.selectedIndex = index + //selected: screensLayout.selectedIndex === index + } + } } } Button { - text: qsTr("Partager") + visible: mainItem.screenSharingAvailable + text: mainItem.conference && mainItem.conference.core.isLocalScreenSharing + ? qsTr("Stop") + : qsTr("Partager") + onClicked: mainItem.conference.core.lToggleScreenSharing() } -} \ No newline at end of file +} diff --git a/Linphone/view/Item/TabBar.qml b/Linphone/view/Item/TabBar.qml index df37c6516..310a83d40 100644 --- a/Linphone/view/Item/TabBar.qml +++ b/Linphone/view/Item/TabBar.qml @@ -46,8 +46,7 @@ Control.TabBar { Control.TabButton { required property string modelData required property int index - // width: Math.min(txtMeter.advanceWidth, Math.max(50, mainItem.width - (x - mainItem.x))) - width: txtMeter.advanceWidth + width: implicitWidth hoverEnabled: true ToolTip { visible: tabText.truncated && hovered @@ -70,22 +69,15 @@ Control.TabBar { contentItem: Text { id: tabText - anchors.fill: parent + width: Math.min(implicitWidth, mainItem.width / mainItem.model.length) font.weight: mainItem.textWeight color: mainItem.currentIndex === index ? DefaultStyle.main2_600 : DefaultStyle.main2_400 font.family: DefaultStyle.defaultFont font.pixelSize: mainItem.pixelSize elide: Text.ElideRight maximumLineCount: 1 - text: txtMeter.elidedText - // width: Math.min(txtMeter.advanceWidth, Math.max(50, mainItem.width - (x - mainItem.x))) - bottomPadding: 5 * DefaultStyle.dp - } - - TextMetrics { - id: txtMeter - font: tabText.font text: modelData + bottomPadding: 5 * DefaultStyle.dp } } } diff --git a/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml b/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml index ce848b3a9..91a0cb375 100644 --- a/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml +++ b/Linphone/view/Layout/Call/ActiveSpeakerLayout.qml @@ -109,8 +109,8 @@ Item{ id: activeSpeakerSticker previewEnabled: false call: mainItem.call - width: parent.width - height: parent.height + width: mainStackView.width + height: mainStackView.height participantDevice: mainItem.conference && mainItem.conference.core.activeSpeaker property var address: participantDevice && participantDevice.core.address videoEnabled: (participantDevice && participantDevice.core.videoEnabled) || (!participantDevice && call && call.core.remoteVideoEnabled) @@ -125,18 +125,19 @@ Item{ } } ListView{ + id: sideStickers Layout.fillHeight: true Layout.preferredWidth: 300 * DefaultStyle.dp Layout.rightMargin: 10 * DefaultStyle.dp Layout.bottomMargin: 10 * DefaultStyle.dp - visible: allDevices.count > 2 + visible: allDevices.count > 2 || !!mainItem.conference?.core.isScreenSharingEnabled //spacing: 15 * DefaultStyle.dp // bugged? First item has twice margins model: allDevices snapMode: ListView.SnapOneItem clip: true delegate: Item{ // Spacing workaround visible: $modelData && mainItem.callState != LinphoneEnums.CallState.End && mainItem.callState != LinphoneEnums.CallState.Released - && $modelData.core.address != activeSpeakerAddress || false + && ($modelData.core.address != activeSpeakerAddress || mainItem.conference?.core.isScreenSharingEnabled) || false height: visible ? (180 + 15) * DefaultStyle.dp : 0 width: 300 * DefaultStyle.dp Sticker { @@ -157,7 +158,7 @@ Item{ id: preview qmlName: 'P' previewEnabled: true - visible: mainItem.call && allDevices.count <= 2 + visible: !sideStickers.visible onVisibleChanged: console.log(visible + " : " +allDevices.count) height: 180 * DefaultStyle.dp width: 300 * DefaultStyle.dp diff --git a/cmake/GenerateAppMacos.cmake b/cmake/GenerateAppMacos.cmake index fa02454e4..70b72ee6a 100644 --- a/cmake/GenerateAppMacos.cmake +++ b/cmake/GenerateAppMacos.cmake @@ -113,6 +113,9 @@ foreach(_FILE IN LISTS ${_BINARIES}) endif() endif() endforeach() +execute_process(COMMAND codesign --force --deep --sign - "${MAIN_INSTALL_DIR}/${LINPHONEAPP_EXENAME}.app")#If not code signed, app can crash because of APPLE on "Code Signature Invalid". + + #[[ if(NOT ENABLE_FAT_BINARY) # Generate XCFrameworks diff --git a/cmake/TasksMacos.cmake b/cmake/TasksMacos.cmake index b01242b6b..81a1b4316 100644 --- a/cmake/TasksMacos.cmake +++ b/cmake/TasksMacos.cmake @@ -20,13 +20,22 @@ # ############################################################################ -set(LINPHONEAPP_MACOS_ARCHS "x86_64" CACHE STRING "MacOS architectures to build: comma-separated list of values in [arm64, x86_64]") option(ENABLE_FAT_BINARY "Enable fat binary generation using lipo." ON) set(_MACOS_ARCHS ${LINPHONEAPP_MACOS_ARCHS}) +#linphone_sdk_convert_comma_separated_list_to_cmake_list("${LINPHONESDK_MACOS_ARCHS}" _MACOS_ARCHS) +macro(linphone_app_convert_comma_separated_list_to_cmake_list INPUT OUTPUT) + string(REPLACE " " "" ${OUTPUT} "${INPUT}") + string(REPLACE "," ";" ${OUTPUT} "${${OUTPUT}}") +endmacro() +linphone_app_convert_comma_separated_list_to_cmake_list("${LINPHONEAPP_MACOS_ARCHS}" _MACOS_ARCHS) + + + #linphone_sdk_convert_comma_separated_list_to_cmake_list("${LINPHONESDK_MACOS_ARCHS}" _MACOS_ARCHS) set(LINPHONEAPP_NAME "Linphone") +set(LINPHONEAPP_EXENAME "Linphone60") set(LINPHONEAPP_PLATFORM "macos") set(SUB_TARGET app_macos) string(TOLOWER "${LINPHONEAPP_PLATFORM}" LINPHONEAPP_PLATFORM_LOWER) @@ -40,15 +49,15 @@ set(_MACOS_INSTALL_DIR "${APPLICATION_OUTPUT_DIR}/${_MACOS_INSTALL_RELATIVE_DIR} #linphone_sdk_get_inherited_cmake_args(_CMAKE_CONFIGURE_ARGS _CMAKE_BUILD_ARGS) #linphone_sdk_get_enable_cmake_args(_MACOS_CMAKE_ARGS) -set(_MACOS_CMAKE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}) +set(_MACOS_CMAKE_ARGS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}") set(_MACOS_TARGETS) foreach(_MACOS_ARCH IN LISTS _MACOS_ARCHS) set(_TARGET_NAME ${SUB_TARGET}-${_MACOS_ARCH}) # app_macos-x86_64 set(_MACOS_ARCH_BINARY_DIR "${PROJECT_BINARY_DIR}/${_TARGET_NAME}") # build/app_macos-x86_64 - set(_MACOS_ARCH_INSTALL_DIR "${_MACOS_INSTALL_DIR}-${_MACOS_ARCH}") # build/OUTPUT/Linphone/macos-x86_64 + set(_MACOS_ARCH_INSTALL_DIR "${_MACOS_INSTALL_DIR}-${_MACOS_ARCH}") # build/OUTPUT/linphone-app/macos-x86_64 add_custom_target(${_TARGET_NAME} ALL - COMMAND ${CMAKE_COMMAND} -B ${_MACOS_ARCH_BINARY_DIR} -DMONO_ARCH=${_MACOS_ARCH} ${USER_ARGS} ${OPTION_LIST} ${_MACOS_CMAKE_ARGS} -DLINPHONEAPP_INSTALL_PREFIX=${_MACOS_ARCH_INSTALL_DIR} -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} -DCMAKE_TOOLCHAIN_FILE=${PROJECT_SOURCE_DIR}/cmake/toolchains/toolchain-mac-x86_64.cmake -DLINPHONEAPP_BUILD_TYPE="Normal" + COMMAND ${CMAKE_COMMAND} -B ${_MACOS_ARCH_BINARY_DIR} -DMONO_ARCH=${_MACOS_ARCH} ${USER_ARGS} ${OPTION_LIST} ${_MACOS_CMAKE_ARGS} -DLINPHONEAPP_INSTALL_PREFIX=${_MACOS_ARCH_INSTALL_DIR} -DCMAKE_TOOLCHAIN_FILE=${PROJECT_SOURCE_DIR}/cmake/toolchains/toolchain-mac-${_MACOS_ARCH}.cmake -DLINPHONEAPP_BUILD_TYPE="Normal" COMMAND ${CMAKE_COMMAND} --build ${_MACOS_ARCH_BINARY_DIR} --target install ${_CMAKE_BUILD_ARGS} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Building Linphone APP for MacOS ${_MACOS_ARCH}" @@ -68,6 +77,7 @@ add_custom_target(gen-apps ALL "-DLINPHONEAPP_BUILD_DIR=${CMAKE_BINARY_DIR}" "-DLINPHONEAPP_MACOS_ARCHS=${LINPHONEAPP_MACOS_ARCHS}" "-DLINPHONEAPP_NAME=${_MACOS_INSTALL_RELATIVE_DIR}" + "-DLINPHONEAPP_EXENAME=${LINPHONEAPP_EXENAME}" "-DLINPHONEAPP_PLATFORM=${LINPHONEAPP_PLATFORM_LOWER}" "-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" "-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}" diff --git a/cmake/install/cleanCPack.cmake.in b/cmake/install/cleanCPack.cmake.in index 53246b1f5..fe4d3fb5a 100644 --- a/cmake/install/cleanCPack.cmake.in +++ b/cmake/install/cleanCPack.cmake.in @@ -52,6 +52,8 @@ if(APPLE) if (DO_SIGNING) execute_process(COMMAND bash "@CMAKE_SOURCE_DIR@/cmake/install/sign_package.sh" codesign "@LINPHONE_BUILDER_SIGNING_IDENTITY@" "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/ALL_IN_ONE") #execute_process(COMMAND codesign --entitlements" "@CMAKE_CURRENT_BINARY_DIR@/../../entitlements.xml" "--force" "--deep" "--timestamp" "--options" "runtime,library" "--verbose" "-s" "@LINPHONE_BUILDER_SIGNING_IDENTITY@" "@APPLICATION_OUTPUT_DIR@/@APPLICATION_NAME@.app") + else() + execute_process(COMMAND codesign --force --deep --sign "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/ALL_IN_ONE/@APPLICATION_NAME@.app" )#If not code signed, app can crash because of APPLE on "Code Signature Invalid" (spotted for ARM64) endif() elseif(WIN32) message(STATUS "Execute : @DEPLOYQT_PROGRAM@ ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/@CMAKE_INSTALL_BINDIR@/@EXECUTABLE_NAME@.exe --qmldir=@LINPHONE_QML_DIR@ --verbose=2 --no-compiler-runtime") diff --git a/cmake/install/install.cmake b/cmake/install/install.cmake index c93cf3d13..89a60edb9 100644 --- a/cmake/install/install.cmake +++ b/cmake/install/install.cmake @@ -96,10 +96,10 @@ if(APPLE) if (NOT DEPLOYQT_PROGRAM) message(FATAL_ERROR "Could not find the macdeployqt program. Make sure it is in the PATH.") endif() - #Packaging is done by CPack in the cleanCpack.cmake file. But on mac, we need Qt files in .app + #Packaging is done by CPack in the cleanCPack.cmake file. But on mac, we need Qt files in .app if(NOT ENABLE_APP_PACKAGING) - install(CODE "MESSAGE(\"MacDeploy install: execute_process(COMMAND ${DEPLOYQT_PROGRAM} ${APPLICATION_OUTPUT_DIR}/${APPLICATION_NAME}.app -qmldir=${LINPHONE_QML_DIR} -no-strip -verbose=2 -always-overwrite) \")") - install(CODE "execute_process(COMMAND ${DEPLOYQT_PROGRAM} ${APPLICATION_OUTPUT_DIR}/${APPLICATION_NAME}.app -qmldir=${LINPHONE_QML_DIR} -no-strip -verbose=2 -always-overwrite)") + install(CODE "MESSAGE(\"MacDeploy install: execute_process(COMMAND ${DEPLOYQT_PROGRAM} ${APPLICATION_OUTPUT_DIR}/${APPLICATION_NAME}.app -qmldir=${LINPHONE_QML_DIR} -no-strip -verbose=0 -always-overwrite) \")") + install(CODE "execute_process(COMMAND ${DEPLOYQT_PROGRAM} ${APPLICATION_OUTPUT_DIR}/${APPLICATION_NAME}.app -qmldir=${LINPHONE_QML_DIR} -no-strip -verbose=0 -always-overwrite)") endif() elseif(WIN32) set(BIN_ARCH "win32") @@ -175,7 +175,7 @@ if(${ENABLE_APP_PACKAGING}) set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/Linphone/data/icon.ico") set(PERFORM_SIGNING 0) - configure_file("${CMAKE_SOURCE_DIR}/cmake/install/cleanCpack.cmake.in" "${CMAKE_BINARY_DIR}/cmake/install/cleanCpack.cmake" @ONLY) + configure_file("${CMAKE_SOURCE_DIR}/cmake/install/cleanCPack.cmake.in" "${CMAKE_BINARY_DIR}/cmake/install/cleanCPack.cmake" @ONLY) set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_BINARY_DIR}/cmake/install/cleanCPack.cmake") if(APPLE) diff --git a/cmake/install/macos/Info.plist.in b/cmake/install/macos/Info.plist.in index e36a72d02..e15676a2b 100644 --- a/cmake/install/macos/Info.plist.in +++ b/cmake/install/macos/Info.plist.in @@ -51,6 +51,8 @@ + LSApplicationCategoryType + public.app-category.social-networking NSPrincipalClass NSApplication NSHighResolutionCapable diff --git a/cmake/toolchains/toolchain-mac-arm64.cmake b/cmake/toolchains/toolchain-mac-arm64.cmake index 213974cc1..a3ec58f56 100644 --- a/cmake/toolchains/toolchain-mac-arm64.cmake +++ b/cmake/toolchains/toolchain-mac-arm64.cmake @@ -16,7 +16,7 @@ # along with this program. If not, see . # ################################################################################ -message(FATAL_ERROR A) + set(CMAKE_SYSTEM_PROCESSOR "arm64") set(CMAKE_OSX_ARCHITECTURES "arm64") set(CLANG_TARGET "arm64-apple-macos") diff --git a/external/linphone-sdk b/external/linphone-sdk index 54bb7fb4d..ea3bb6f22 160000 --- a/external/linphone-sdk +++ b/external/linphone-sdk @@ -1 +1 @@ -Subproject commit 54bb7fb4d84141bb94042a09b157257df84d8be9 +Subproject commit ea3bb6f2284ce16383c3251b9b899dc9b06d0ead