Screensharing + Mac ARM64 + Linux build

This commit is contained in:
Julien Wadel 2024-04-25 17:25:06 +02:00
parent 231d42db1c
commit 99f752390b
55 changed files with 2452 additions and 74 deletions

View file

@ -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)

View file

@ -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_PROPERTY:${T},INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(${TARGET_NAME} PUBLIC ${T})

View file

@ -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")

View file

@ -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

View file

@ -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<ParticipantDeviceGui>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui");
qmlRegisterType<ParticipantDeviceProxy>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy");
qmlRegisterUncreatableType<ScreenList>(Constants::MainQmlUri, 1, 0, "ScreenList", QLatin1String("Uncreatable"));
qmlRegisterType<ScreenProxy>(Constants::MainQmlUri, 1, 0, "ScreenProxy");
qmlRegisterUncreatableType<VideoSourceDescriptorCore>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorCore",
QLatin1String("Uncreatable"));
qmlRegisterType<VideoSourceDescriptorGui>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui");
LinphoneEnums::registerMetaTypes();
}

View file

@ -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

View file

@ -102,6 +102,8 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &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<CallCore> 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<VideoSourceDescriptorCore> core) {
if (mVideoSourceDescriptor != core) {
mVideoSourceDescriptor = core;
emit videoSourceDescriptorChanged();
}
}
void CallCore::setConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mConferenceVideoLayout != layout) {

View file

@ -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<CallCore> create(const std::shared_ptr<linphone::Call> &call);
@ -149,8 +153,10 @@ public:
LinphoneEnums::ConferenceLayout getConferenceVideoLayout() const;
void setConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout);
std::shared_ptr<CallModel> getModel() const;
VideoSourceDescriptorGui *getVideoSourceDescriptorGui() const;
void setVideoSourceDescriptor(QSharedPointer<VideoSourceDescriptorCore> core);
std::shared_ptr<CallModel> 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<CallModel> mCallModel;
QSharedPointer<ConferenceCore> mConference;
QSharedPointer<VideoSourceDescriptorCore> mVideoSourceDescriptor;
LinphoneEnums::CallStatus mStatus;
LinphoneEnums::CallState mState;
LinphoneEnums::CallState mTransferState;

View file

@ -39,6 +39,8 @@ ConferenceCore::ConferenceCore(const std::shared_ptr<linphone::Conference> &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<ConferenceCore> me) {
[this](const std::shared_ptr<linphone::ParticipantDevice> &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<ConferenceModel> ConferenceCore::getModel() const {
return mConferenceModel;
}

View file

@ -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<ConferenceModel> getModel() const;
//---------------------------------------------------------------------------
signals:
void isReadyChanged();
void isLocalScreenSharingChanged();
void isScreenSharingEnabledChanged();
void participantDeviceCountChanged();
void activeSpeakerChanged();
void lToggleScreenSharing();
private:
QSharedPointer<SafeConnection<ConferenceCore, ConferenceModel>> mConferenceModelConnection;
std::shared_ptr<ConferenceModel> mConferenceModel;
@ -83,6 +95,8 @@ private:
int mParticipantDeviceCount = 0;
bool mIsReady = false;
bool mIsLocalScreenSharing = false;
bool mIsScreenSharingEnabled = false;
QString mSubject;
QDateTime mStartDate = QDateTime::currentDateTime();

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ScreenList.hpp"
#include <QGuiApplication>
#include <QPixmap>
#include <QScreen>
#include <QWindow>
#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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SCREEN_LIST_H_
#define SCREEN_LIST_H_
#include "core/proxy/AbstractListProxy.hpp"
#include <QScreen>
class ScreenList : public AbstractListProxy<QVariantMap> {
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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <QQuickWindow>
#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<ScreenList *>(sourceModel())->getMode();
}
void ScreenProxy::setMode(ScreenList::Mode data) {
dynamic_cast<ScreenList *>(sourceModel())->setMode(data);
}
void ScreenProxy::update() {
dynamic_cast<ScreenList *>(sourceModel())->update();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "VideoSourceDescriptorCore.hpp"
#include "core/App.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
// =============================================================================
DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorCore)
QSharedPointer<VideoSourceDescriptorCore>
VideoSourceDescriptorCore::create(const std::shared_ptr<linphone::VideoSourceDescriptor> &desc) {
auto sharedPointer =
QSharedPointer<VideoSourceDescriptorCore>(new VideoSourceDescriptorCore(desc), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
VideoSourceDescriptorCore::VideoSourceDescriptorCore(const std::shared_ptr<linphone::VideoSourceDescriptor> &desc) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mustBeInLinphoneThread(getClassName());
mVideoDescModel = Utils::makeQObject_ptr<VideoSourceDescriptorModel>(desc);
mScreenIndex = mVideoDescModel->getScreenSharingIndex();
mWindowId = mVideoDescModel->getWindowId();
}
VideoSourceDescriptorCore::~VideoSourceDescriptorCore() {
}
void VideoSourceDescriptorCore::setSelf(QSharedPointer<VideoSourceDescriptorCore> me) {
mVideoDescModelConnection = QSharedPointer<SafeConnection<VideoSourceDescriptorCore, VideoSourceDescriptorModel>>(
new SafeConnection<VideoSourceDescriptorCore, VideoSourceDescriptorModel>(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<VideoSourceDescriptorModel> VideoSourceDescriptorCore::getModel() {
return mVideoDescModel;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_SOURCE_DESCRIPTOR_CORE_H_
#define VIDEO_SOURCE_DESCRIPTOR_CORE_H_
#include <linphone++/linphone.hh>
// =============================================================================
#include <QDateTime>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#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<VideoSourceDescriptorCore>
create(const std::shared_ptr<linphone::VideoSourceDescriptor> &desc);
VideoSourceDescriptorCore(const std::shared_ptr<linphone::VideoSourceDescriptor> &desc);
virtual ~VideoSourceDescriptorCore();
void setSelf(QSharedPointer<VideoSourceDescriptorCore> me);
int getScreenSharingIndex() const;
void setScreenSharingDisplay(int index);
quint64 getWindowId() const;
void setWindowId(quint64 id);
std::shared_ptr<VideoSourceDescriptorModel> 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<VideoSourceDescriptorModel> mVideoDescModel;
QSharedPointer<SafeConnection<VideoSourceDescriptorCore, VideoSourceDescriptorModel>> mVideoDescModelConnection;
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(VideoSourceDescriptorCore *)
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "VideoSourceDescriptorGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorGui)
VideoSourceDescriptorGui::VideoSourceDescriptorGui(QSharedPointer<VideoSourceDescriptorCore> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_SOURCE_DESCRIPTOR_GUI_H_
#define VIDEO_SOURCE_DESCRIPTOR_GUI_H_
#include "VideoSourceDescriptorCore.hpp"
#include <QObject>
#include <QSharedPointer>
class VideoSourceDescriptorGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(VideoSourceDescriptorCore *core READ getCore CONSTANT)
public:
VideoSourceDescriptorGui(QSharedPointer<VideoSourceDescriptorCore> core);
~VideoSourceDescriptorGui();
VideoSourceDescriptorCore *getCore() const;
QSharedPointer<VideoSourceDescriptorCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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)

View file

@ -330,6 +330,32 @@ void CallModel::updateConferenceVideoLayout() {
}
}
void CallModel::setVideoSource(std::shared_ptr<linphone::VideoSourceDescriptor> 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<VideoSourceDescriptorModel> model) {
if (model) setVideoSource(model->mDesc);
else {
setVideoSource(nullptr);
}
}
void CallModel::onDtmfReceived(const std::shared_ptr<linphone::Call> &call, int dtmf) {
emit dtmfReceived(call, dtmf);
}

View file

@ -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<linphone::VideoSourceDescriptor> videoDesc);
LinphoneEnums::VideoSourceScreenSharingType getVideoSourceType() const;
int getScreenSharingIndex() const;
void setVideoSourceDescriptorModel(std::shared_ptr<VideoSourceDescriptorModel> model = nullptr);
static void activateLocalVideo(std::shared_ptr<linphone::CallParams> &params,
const std::shared_ptr<const linphone::CallParams> &currentParams,
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;

View file

@ -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<linphone::Conference> &co
: ::Listener<linphone::Conference, linphone::ConferenceListener>(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<const linphone::AudioDevice> 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<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
@ -143,10 +172,12 @@ void ConferenceModel::onActiveSpeakerParticipantDevice(
void ConferenceModel::onParticipantAdded(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<linphone::Participant> &participant) {
emit participantAdded(participant);
emit participantDeviceCountChanged(getParticipantDeviceCount());
}
void ConferenceModel::onParticipantRemoved(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::Participant> &participant) {
emit participantRemoved(participant);
emit participantDeviceCountChanged(getParticipantDeviceCount());
}
void ConferenceModel::onParticipantDeviceAdded(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<linphone::ParticipantDevice> &participantDevice) {
@ -156,6 +187,7 @@ void ConferenceModel::onParticipantDeviceAdded(const std::shared_ptr<linphone::C
for (auto d : conference->getMe()->getDevices())
lDebug() << "\t--> " << d->getAddress()->asString().c_str();
emit participantDeviceAdded(participantDevice);
emit participantDeviceCountChanged(getParticipantDeviceCount());
}
void ConferenceModel::onParticipantDeviceRemoved(
const std::shared_ptr<linphone::Conference> &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<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &device,
@ -201,9 +235,28 @@ void ConferenceModel::onParticipantDeviceIsSpeakingChanged(
// ". Speaking:" << isSpeaking;
emit participantDeviceIsSpeakingChanged(participantDevice, isSpeaking);
}
void ConferenceModel::onParticipantDeviceScreenSharingChanged(
const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &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<linphone::Conference> &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<linphone::Conference> &conference,
@ -215,3 +268,12 @@ void ConferenceModel::onAudioDeviceChanged(const std::shared_ptr<linphone::Confe
const std::shared_ptr<const linphone::AudioDevice> &audioDevice) {
lDebug() << "onAudioDeviceChanged is not yet implemented.";
}
void ConferenceModel::onIsScreenSharingEnabledChanged() {
auto call = mMonitor->getCall();
std::shared_ptr<linphone::CallParams> params = CoreModel::getInstance()->getCore()->createCallParams(call);
if (params->getConferenceVideoLayout() == linphone::Conference::Layout::Grid && params->videoEnabled()) {
params->setConferenceVideoLayout(linphone::Conference::Layout::ActiveSpeaker);
}
call->update(params);
}

View file

@ -49,6 +49,10 @@ public:
void setOutputAudioDevice(const std::shared_ptr<linphone::AudioDevice> &id);
std::shared_ptr<const linphone::AudioDevice> getOutputAudioDevice() const;
void toggleScreenSharing();
bool isLocalScreenSharing() const;
bool isScreenSharingEnabled() const;
void setPaused(bool paused);
void removeParticipant(const std::shared_ptr<linphone::Participant> &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<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice,
bool isSpeaking) override;
virtual void
onParticipantDeviceScreenSharingChanged(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &device,
bool enabled) override;
virtual void onStateChanged(const std::shared_ptr<linphone::Conference> &conference,
linphone::Conference::State newState) override;
virtual void onSubjectChanged(const std::shared_ptr<linphone::Conference> &conference,
const std::string &subject) override;
virtual void onAudioDeviceChanged(const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::AudioDevice> &audioDevice) override;
const std::shared_ptr<const linphone::AudioDevice> &audioDevice) override;
signals:
void activeSpeakerParticipantDevice(const std::shared_ptr<linphone::ParticipantDevice> &participantDevice);
@ -125,6 +138,8 @@ signals:
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice);
void participantDeviceIsSpeakingChanged(const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice,
bool isSpeaking);
void participantDeviceScreenSharingChanged(const std::shared_ptr<const linphone::ParticipantDevice> &device,
bool enabled);
void conferenceStateChanged(linphone::Conference::State newState);
void subjectChanged(const std::string &subject);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "VideoSourceDescriptorModel.hpp"
#include "tool/native/DesktopTools.hpp"
// =============================================================================
DEFINE_ABSTRACT_OBJECT(VideoSourceDescriptorModel)
VideoSourceDescriptorModel::VideoSourceDescriptorModel() {
}
VideoSourceDescriptorModel::VideoSourceDescriptorModel(std::shared_ptr<linphone::VideoSourceDescriptor> 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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_SOURCE_DESCRIPTOR_MODEL_H_
#define VIDEO_SOURCE_DESCRIPTOR_MODEL_H_
// =============================================================================
#include "tool/AbstractObject.hpp"
#include "tool/LinphoneEnums.hpp"
#include <QObject>
#include <QTimer>
#include <linphone++/linphone.hh>
class VideoSourceDescriptorModel : public QObject, public AbstractObject {
Q_OBJECT
public:
VideoSourceDescriptorModel();
VideoSourceDescriptorModel(std::shared_ptr<linphone::VideoSourceDescriptor> 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<linphone::VideoSourceDescriptor> mDesc;
int mScreenIndex = -1;
signals:
void videoDescriptorChanged();
private:
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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)

View file

@ -42,6 +42,7 @@ void LinphoneEnums::registerMetaTypes() {
qRegisterMetaType<LinphoneEnums::RegistrationState>();
qRegisterMetaType<LinphoneEnums::TunnelMode>();
qRegisterMetaType<LinphoneEnums::TransportType>();
qRegisterMetaType<LinphoneEnums::VideoSourceScreenSharingType>();
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<linphone::VideoSourceScreenSharingType>(type);
}
LinphoneEnums::VideoSourceScreenSharingType
LinphoneEnums::fromLinphone(const linphone::VideoSourceScreenSharingType &type) {
return static_cast<LinphoneEnums::VideoSourceScreenSharingType>(type);
}

View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef DESKTOP_TOOLS_H_
#define DESKTOP_TOOLS_H_
#include <QtGlobal>
#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_

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "DesktopToolsLinux.hpp"
#include "config.h"
#include <QDebug>
#include <QPixmap>
#include <QRect>
#include <QThread>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <fcntl.h>
#include <unistd.h>
#include <X11/cursorfont.h>
// =============================================================================
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<QVariantMap> DesktopTools::getWindows() {
#ifdef ENABLE_SCREENSHARING
QList<QVariantMap> 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<QVariantMap>();
#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<QVariantMap> 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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<QVariantMap> getWindows();
static QImage takeScreenshot(void *window);
static QImage getWindowIcon(void *window);
static void *getDisplay(uintptr_t screenIndex) {
return reinterpret_cast<void *>(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_

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef DESKTOP_TOOLS_MAC_OS_H_
#define DESKTOP_TOOLS_MAC_OS_H_
#include <QObject>
#include <QImage>
#include <QVariantList>
#include <QList>
// =============================================================================
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<QVariantMap> 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_

View file

@ -0,0 +1,228 @@
#include "DesktopToolsMacOs.hpp"
#include "config.h"
#import <AVFoundation/AVFoundation.h>
#import <ScreenCaptureKit/ScreenCaptureKit.h>
#include <QDebug>
#include <QRect>
#include <QThread>
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<const void **>(&id), 1, NULL);
CFArrayRef descriptions = CGWindowListCreateDescriptionFromArray(ids);
bool minimized = false;
if (descriptions && CFArrayGetCount(descriptions)) {
CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(descriptions, 0));
minimized = ! reinterpret_cast<CFBooleanRef>(CFDictionaryGetValue(window, kCGWindowIsOnscreen));
}
CFRelease(descriptions);
CFRelease(ids);
return minimized;
}
QList<QVariantMap> DesktopTools::getWindows() {
QList<QVariantMap> 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<CFStringRef>(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<CFDictionaryRef>(CFArrayGetValueAtIndex(infos, 0));
CFNumberRef pidRef = reinterpret_cast<CFNumberRef>(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<void*>(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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "DesktopToolsWindows.hpp"
#include <QDebug>
#include <QRect>
#include <Windows.h>
#include <atlimage.h>
#include <dwmapi.h>
#include <wingdi.h>
#include <winuser.h>
// =============================================================================
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<QVariantMap> &windowsMap = *reinterpret_cast<QList<QVariantMap> *>(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<QVariantMap> DesktopTools::getWindows() {
#ifdef ENABLE_SCREENSHARING
QList<QVariantMap> windowsMap;
EnumWindows(getWindowsCb, reinterpret_cast<LPARAM>(&windowsMap));
return windowsMap;
#else
return QList<QVariantMap>();
#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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef DESKTOP_TOOLS_WINDOWS_H_
#define DESKTOP_TOOLS_WINDOWS_H_
#include <QImage>
#include <QObject>
#include <QVariantMap>
#include <windows.h>
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<QVariantMap> getWindows();
static QImage takeScreenshot(void *window);
static QImage getWindowIcon(void *window);
static void *getDisplay(uintptr_t screenIndex) {
return reinterpret_cast<void *>(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_

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <QCoreApplication>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDebug>
#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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SCREEN_SAVER_DBUS_H_
#define SCREEN_SAVER_DBUS_H_
#include <QDBusInterface>
// =============================================================================
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_

View file

@ -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 <IOKit/pwr_mgt/IOPMLib.h>
// =============================================================================
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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <QProcess>
#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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SCREEN_SAVER_XDG_H_
#define SCREEN_SAVER_XDG_H_
#include <QTimer>
// =============================================================================
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_

View file

@ -0,0 +1,20 @@
#include "../DesktopToolsMacOs.hpp"
#import <Foundation/NSString.h>
#import <Foundation/NSProcessInfo.h>
// 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];
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ScreenProvider.hpp"
#include "tool/native/DesktopTools.hpp"
#include <QGuiApplication>
#include <QScreen>
#include <QThread>
#include <QWindow>
// =============================================================================
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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SCREEN_PROVIDER_H_
#define SCREEN_PROVIDER_H_
#include <QQuickImageProvider>
// =============================================================================
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

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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}"

View file

@ -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")

View file

@ -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)

View file

@ -51,6 +51,8 @@
</array>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>

View file

@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
message(FATAL_ERROR A)
set(CMAKE_SYSTEM_PROCESSOR "arm64")
set(CMAKE_OSX_ARCHITECTURES "arm64")
set(CLANG_TARGET "arm64-apple-macos")

@ -1 +1 @@
Subproject commit 54bb7fb4d84141bb94042a09b157257df84d8be9
Subproject commit ea3bb6f2284ce16383c3251b9b899dc9b06d0ead