Uri Handler + Restart + Remote provisioning + Confirmation dialog with c++ callback behavior

This commit is contained in:
Julien Wadel 2024-06-12 10:40:40 +02:00
parent 891f9acd8c
commit 4631ea7fe7
24 changed files with 1085 additions and 49 deletions

View file

@ -28,6 +28,7 @@
#include <QFontDatabase>
#include <QGuiApplication>
#include <QLibraryInfo>
#include <QProcessEnvironment>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlFileSelector>
@ -74,14 +75,21 @@
#include "tool/providers/AvatarProvider.hpp"
#include "tool/providers/ImageProvider.hpp"
#include "tool/providers/ScreenProvider.hpp"
#include "tool/request/RequestDialog.hpp"
#include "tool/thread/Thread.hpp"
DEFINE_ABSTRACT_OBJECT(App)
#ifdef Q_OS_LINUX
const QString ApplicationsDirectory(QDir::homePath().append(QStringLiteral("/.local/share/applications/")));
const QString IconsDirectory(QDir::homePath().append(QStringLiteral("/.local/share/icons/hicolor/scalable/apps/")));
#endif
App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
// Do not use APPLICATION_NAME here.
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
@ -101,6 +109,7 @@ App::App(int &argc, char *argv[])
//-------------------
mLinphoneThread = new Thread(this);
init();
lInfo() << QStringLiteral("Starting application " APPLICATION_NAME " (bin: " EXECUTABLE_NAME
"). Version:%1 Os:%2 Qt:%3")
@ -126,6 +135,50 @@ void App::setSelf(QSharedPointer<App>(me)) {
lDebug() << "App : call created" << callGui;
});
});
mCoreModelConnection->makeConnectToModel(&CoreModel::requestRestart, [this]() {
mCoreModelConnection->invokeToCore([this]() {
lInfo() << log().arg("Restarting");
restart();
});
});
mCoreModelConnection->makeConnectToModel(&CoreModel::requestFetchConfig, [this](QString path) {
mCoreModelConnection->invokeToCore([this, path]() {
auto callback = [this, path]() {
RequestDialog *obj = new RequestDialog(
tr("Voulez-vous télécharger et appliquer la configuration depuis cette adresse ?"), path);
connect(obj, &RequestDialog::result, this, [this, obj, path](int result) {
if (result == 1) {
mCoreModelConnection->invokeToModel(
[this, path]() { CoreModel::getInstance()->setFetchConfig(path); });
} else if (result == 0) {
mCoreModelConnection->invokeToModel([]() { CliModel::getInstance()->resetProcesses(); });
}
obj->deleteLater();
});
QMetaObject::invokeMethod(getMainWindow(), "showConfirmationPopup", QVariant::fromValue(obj));
};
if (!getMainWindow()) { // Delay
connect(this, &App::mainWindowChanged, this, callback, Qt::SingleShotConnection);
} else {
callback();
}
});
});
//---------------------------------------------------------------------------------------------
mCliModelConnection = QSharedPointer<SafeConnection<App, CliModel>>(
new SafeConnection<App, CliModel>(me, CliModel::getInstance()), &QObject::deleteLater);
mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) {
QString command(byteArray);
if (command.isEmpty())
mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->runProcess(); });
else {
qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command);
mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->executeCommand(command); });
}
});
mCliModelConnection->makeConnectToModel(&CliModel::showMainWindow, [this]() {
mCliModelConnection->invokeToCore([this]() { Utils::smartShowWindow(getMainWindow()); });
});
}
App *App::getInstance() {
@ -140,12 +193,33 @@ Notifier *App::getNotifier() const {
//-----------------------------------------------------------
void App::init() {
// Console Commands
createCommandParser();
mParser->parse(this->arguments());
// TODO : Update languages for command translations.
createCommandParser(); // Recreate parser in order to use translations from config.
mParser->process(*this);
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
if (!mLinphoneThread->isRunning()) {
lDebug() << log().arg("Starting Thread");
mLinphoneThread->start();
}
setQuitOnLastWindowClosed(true); // TODO: use settings to set it
lInfo() << log().arg("Display server : %1").arg(platformName());
}
void App::initCore() {
// Core. Manage the logger so it must be instantiate at first.
auto coreModel = CoreModel::create("", mLinphoneThread);
connect(
mLinphoneThread, &QThread::started, coreModel.get(),
[this, coreModel]() mutable {
coreModel->start();
CoreModel::create("", mLinphoneThread);
QMetaObject::invokeMethod(
mLinphoneThread->getThreadId(),
[this]() mutable {
CoreModel::getInstance()->start();
auto settings = Settings::create();
QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings]() mutable {
// QML
@ -161,29 +235,27 @@ void App::init() {
mEngine->addImportPath(":/");
mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath());
#ifdef APPLICATION_VENDOR
mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR);
mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR);
#else
mEngine->rootContext()->setContextProperty("applicationVendor", "");
#endif
#ifdef APPLICATION_LICENCE
mEngine->rootContext()->setContextProperty("applicationLicence", APPLICATION_LICENCE);
mEngine->rootContext()->setContextProperty("applicationLicence", APPLICATION_LICENCE);
#else
mEngine->rootContext()->setContextProperty("applicationLicence", "");
#endif
#ifdef APPLICATION_LICENCE_URL
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", APPLICATION_LICENCE_URL);
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", APPLICATION_LICENCE_URL);
#else
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", "");
#endif
#ifdef COPYRIGHT_RANGE_DATE
mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE);
mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE);
#else
mEngine->rootContext()->setContextProperty("copyrightRangeDate", "");
#endif
mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME);
mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME);
mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME);
mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME);
initCppInterfaces();
mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider());
mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider());
@ -205,7 +277,7 @@ void App::init() {
lCritical() << log().arg("Main.qml couldn't be load. The app will exit");
exit(-1);
}
mMainWindow = qobject_cast<QQuickWindow *>(obj);
setMainWindow(qobject_cast<QQuickWindow *>(obj));
QMetaObject::invokeMethod(obj, "initStackViewItem");
Q_ASSERT(mMainWindow);
}
@ -215,27 +287,7 @@ void App::init() {
});
// coreModel.reset();
},
Qt::SingleShotConnection);
// Console Commands
createCommandParser();
mParser->parse(this->arguments());
// TODO : Update languages for command translations.
createCommandParser(); // Recreate parser in order to use translations from config.
mParser->process(*this);
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
if (!mLinphoneThread->isRunning()) {
lDebug() << log().arg("Starting Thread");
mLinphoneThread->start();
}
setQuitOnLastWindowClosed(true); // TODO: use settings to set it
lInfo() << log().arg("Display server : %1").arg(platformName());
// mEngine->load(u"qrc:/Linphone/view/Prototype/CameraPrototype.qml"_qs);
Qt::BlockingQueuedConnection);
}
void App::initCppInterfaces() {
@ -294,6 +346,9 @@ void App::initCppInterfaces() {
QLatin1String("Uncreatable"));
qmlRegisterType<VideoSourceDescriptorGui>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui");
qmlRegisterUncreatableType<RequestDialog>(Constants::MainQmlUri, 1, 0, "RequestDialog",
QLatin1String("Uncreatable"));
LinphoneEnums::registerMetaTypes();
}
@ -309,11 +364,29 @@ void App::clean() {
// mSettings.reset();
// }
qApp->processEvents(QEventLoop::AllEvents, 500);
mLinphoneThread->exit();
mLinphoneThread->wait();
delete mLinphoneThread;
if (mLinphoneThread) {
mLinphoneThread->exit();
mLinphoneThread->wait();
delete mLinphoneThread;
}
}
void App::restart() {
mCoreModelConnection->invokeToModel([this]() {
CoreModel::getInstance()->getCore()->stop();
mCoreModelConnection->invokeToCore([this]() {
mEngine->deleteLater();
if (mSettings) mSettings.reset();
initCore();
// Retrieve self from current Core/Model connection and reset Qt connections.
auto oldConnection = mCoreModelConnection;
oldConnection->mCore.lock();
auto me = oldConnection->mCore.mQData;
setSelf(me);
oldConnection->mCore.unlock();
exit((int)StatusCode::gRestartCode);
});
});
}
void App::createCommandParser() {
if (!mParser) delete mParser;
@ -336,6 +409,28 @@ void App::createCommandParser() {
{"qt-logs-only", tr("commandLineOptionQtLogsOnly")},
});
}
// Should be call only at first start
void App::sendCommand() {
auto arguments = mParser->positionalArguments();
static bool firstStart = true; // We can't erase positional arguments. So we get them on each restart.
if (firstStart && arguments.size() > 0) {
firstStart = false;
if (isSecondary()) { // Send to primary
lDebug() << "Sending " << arguments;
for (auto i : arguments) {
sendMessage(i.toLocal8Bit(), -1);
}
} else { // Execute
lDebug() << "Executing " << arguments;
for (auto i : arguments) {
QString command(i);
receivedMessage(0, i.toLocal8Bit());
}
}
} else if (isPrimary()) { // Run waiting process
receivedMessage(0, "");
}
}
bool App::notify(QObject *receiver, QEvent *event) {
bool done = true;
@ -401,6 +496,122 @@ void App::closeCallsWindow() {
}
}
QQuickWindow *App::getMainWindow() {
QQuickWindow *App::getMainWindow() const {
return mMainWindow;
}
void App::setMainWindow(QQuickWindow *data) {
if (mMainWindow != data) {
mMainWindow = data;
emit mainWindowChanged();
}
}
#ifdef Q_OS_LINUX
QString App::getApplicationPath() const {
const QString binPath(QCoreApplication::applicationFilePath());
// Check if installation is done via Flatpak, AppImage, or classic package
// in order to rewrite a correct exec path for autostart
QString exec;
qDebug() << "binpath=" << binPath;
if (binPath.startsWith("/app")) { // Flatpak
exec = QStringLiteral("flatpak run " APPLICATION_ID);
qDebug() << "exec path autostart set flatpak=" << exec;
} else if (binPath.startsWith("/tmp/.mount")) { // Appimage
exec = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
qDebug() << "exec path autostart set appimage=" << exec;
} else { // classic package
exec = binPath;
qDebug() << "exec path autostart set classic package=" << exec;
}
return exec;
}
void App::exportDesktopFile() {
QDir dir(ApplicationsDirectory);
if (!dir.exists() && !dir.mkpath(ApplicationsDirectory)) {
qWarning() << QStringLiteral("Unable to build applications dir path: `%1`.").arg(ApplicationsDirectory);
return;
}
const QString confPath(ApplicationsDirectory + EXECUTABLE_NAME ".desktop");
if (generateDesktopFile(confPath, true, false)) generateDesktopFile(confPath, false, false);
}
bool App::generateDesktopFile(const QString &confPath, bool remove, bool openInBackground) {
qInfo() << QStringLiteral("Updating `%1`...").arg(confPath);
QFile file(confPath);
if (remove) {
if (file.exists() && !file.remove()) {
qWarning() << QLatin1String("Unable to remove autostart file: `" EXECUTABLE_NAME ".desktop`.");
return false;
}
return true;
}
if (!file.open(QFile::WriteOnly)) {
qWarning() << "Unable to open autostart file: `" EXECUTABLE_NAME ".desktop`.";
return false;
}
QString exec = getApplicationPath();
QDir dir;
QString iconPath;
bool haveIcon = false;
if (!dir.mkpath(IconsDirectory)) // Scalable icons folder may be created
qWarning() << "Cannot create scalable icon path at " << IconsDirectory;
else {
iconPath = IconsDirectory + EXECUTABLE_NAME + ".svg";
QFile icon(Constants::WindowIconPath);
if (!QFile(iconPath).exists()) { // Keep old icon but copy if it doesn't exist
haveIcon = icon.copy(iconPath);
if (!haveIcon) qWarning() << "Couldn't copy icon svg into " << iconPath;
else { // Update permissions
QFile icon(iconPath);
icon.setPermissions(icon.permissions() | QFileDevice::WriteOwner);
}
} else {
qInfo() << "Icon already exists in " << IconsDirectory << ". It is not replaced.";
haveIcon = true;
}
}
QTextStream(&file) << QString("[Desktop Entry]\n"
"Name=" APPLICATION_NAME "\n"
"GenericName=SIP Phone\n"
"Comment=" APPLICATION_DESCRIPTION "\n"
"Type=Application\n")
<< (openInBackground ? "Exec=" + exec + " --iconified %u\n" : "Exec=" + exec + " %u\n")
<< (haveIcon ? "Icon=" + iconPath + "\n" : "Icon=" EXECUTABLE_NAME "\n")
<< "Terminal=false\n"
"Categories=Network;Telephony;\n"
"MimeType=x-scheme-handler/sip-" EXECUTABLE_NAME ";x-scheme-handler/sips-" EXECUTABLE_NAME
";x-scheme-handler/" EXECUTABLE_NAME "-sip;x-scheme-handler/" EXECUTABLE_NAME
"-sips;x-scheme-handler/sip;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/"
"callto;x-scheme-handler/" EXECUTABLE_NAME "-config;\n"
"X-PulseAudio-Properties=media.role=phone\n";
return true;
}
#elif defined(Q_OS_MACOS)
// On MAC, URI handlers call the application with no arguments and pass them in event loop.
bool App::event(QEvent *event) {
if (event->type() == QEvent::FileOpen) {
const QString url = static_cast<QFileOpenEvent *>(event)->url().toString();
if (isSecondary()) {
sendMessage(url.toLocal8Bit(), -1);
::exit(EXIT_SUCCESS);
}
receivedMessage(0, url.toLocal8Bit());
} else if (event->type() == QEvent::ApplicationStateChange) {
auto state = static_cast<QApplicationStateChangeEvent *>(event);
if (state->applicationState() == Qt::ApplicationActive) Utils::smartShowWindow(getMainWindow());
}
return SingleApplication::event(event);
}
#endif

View file

@ -24,6 +24,7 @@
#include "core/setting/SettingsCore.hpp"
#include "core/singleapplication/singleapplication.h"
#include "model/cli/CliModel.hpp"
#include "model/core/CoreModel.hpp"
#include "tool/AbstractObject.hpp"
@ -33,6 +34,7 @@ class Notifier;
class QQuickWindow;
class App : public SingleApplication, public AbstractObject {
Q_OBJECT
public:
App(int &argc, char *argv[]);
~App();
@ -98,21 +100,38 @@ public:
void clean();
void init();
void initCore();
void initCppInterfaces();
void restart();
void onLoggerInitialized();
void sendCommand();
QQuickWindow *getCallsWindow(QVariant callGui = QVariant());
void setCallsWindowProperty(const char *id, QVariant property);
void closeCallsWindow();
QQuickWindow *getMainWindow();
QQuickWindow *getMainWindow() const;
void setMainWindow(QQuickWindow *data);
#ifdef Q_OS_LINUX
Q_INVOKABLE void exportDesktopFile();
QString getApplicationPath() const;
bool generateDesktopFile(const QString &confPath, bool remove, bool openInBackground);
#elif defined(Q_OS_MACOS)
bool event(QEvent *event) override;
#endif
QQmlApplicationEngine *mEngine = nullptr;
bool notify(QObject *receiver, QEvent *event) override;
enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 };
signals:
void mainWindowChanged();
// void executeCommand(QString command);
private:
void createCommandParser();
@ -123,6 +142,7 @@ private:
QQuickWindow *mCallsWindow = nullptr;
QSharedPointer<Settings> mSettings;
QSharedPointer<SafeConnection<App, CoreModel>> mCoreModelConnection;
QSharedPointer<SafeConnection<App, CliModel>> mCliModelConnection;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -296,7 +296,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(int msecs, const QByteArray &
socket->write(msg);
socket->flush();
bool result = socket->waitForReadyRead(msecs); // await ack byte
bool result = socket->waitForReadyRead( msecs < 0 ? -1 : msecs); // await ack byte
if (result) {
socket->read(1);
return true;

View file

@ -11,6 +11,11 @@
#include <QQmlDebuggingEnabler>
#endif
#ifdef _WIN32
#include <Windows.h>
FILE *gStream = NULL;
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 10)
// From 5.15.2 to 5.15.10, sometimes, Accessibility freeze the application : Deactivate handlers.
#define ACCESSBILITY_WORKAROUND
@ -22,14 +27,30 @@ void DummyRootObjectHandler(QObject *) {
}
#endif
void cleanStream() {
#ifdef _WIN32
if (gStream) {
fflush(stdout);
fflush(stderr);
fclose(gStream);
}
#endif
}
int main(int argc, char *argv[]) {
#if defined _WIN32
// log in console only if launched from console
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen_s(&gStream, "CONOUT$", "w", stdout);
freopen_s(&gStream, "CONOUT$", "w", stderr);
}
#endif
// Useful to share camera on Fullscreen (other context) or multiscreens
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
// Disable QML cache. Avoid malformed cache.
qputenv("QML_DISABLE_DISK_CACHE", "true");
auto app = QSharedPointer<App>::create(argc, argv);
app->setSelf(app);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
@ -46,8 +67,20 @@ int main(int argc, char *argv[]) {
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
#endif
if (app->isSecondary()) {
app->sendCommand();
qInfo() << QStringLiteral("Running secondary app success. Kill it now.");
app->clean();
cleanStream();
return EXIT_SUCCESS;
} else {
app->initCore();
app->setSelf(app);
}
int result = 0;
do {
app->sendCommand();
result = app->exec();
} while (result == (int)App::StatusCode::gRestartCode);
qWarning() << "[Main] Exiting app with the code : " << result;

View file

@ -13,6 +13,7 @@ list(APPEND _LINPHONEAPP_SOURCES
model/participant/ParticipantDeviceModel.cpp
model/participant/ParticipantModel.cpp
model/cli/CliModel.cpp
model/core/CoreModel.cpp
model/friend/FriendModel.cpp

View file

@ -0,0 +1,405 @@
/*
* 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 "CliModel.hpp"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QObject>
#include <QSysInfo>
#include <QTimer>
#include <QUrl>
#include "model/core/CoreModel.hpp"
#include "model/tool/ToolModel.hpp"
// =============================================================================
DEFINE_ABSTRACT_OBJECT(CliModel)
std::shared_ptr<CliModel> CliModel::gCliModel;
QMap<QString, CliModel::Command> CliModel::mCommands{
createCommand("show", QT_TR_NOOP("showFunctionDescription"), &CliModel::cliShow, {}, true),
createCommand("fetch-config", QT_TR_NOOP("fetchConfigFunctionDescription"), &CliModel::cliFetchConfig, {}, true),
createCommand("call", QT_TR_NOOP("callFunctionDescription"), &CliModel::cliCall, {{"sip-address", {}}}, true),
/*
createCommand("initiate-conference", QT_TR_NOOP("initiateConferenceFunctionDescription"), cliInitiateConference, {
{ "sip-address", {} }, { "conference-id", {} }
}),
createCommand("join-conference", QT_TR_NOOP("joinConferenceFunctionDescription"), cliJoinConference, {
{ "sip-address", {} }, { "conference-id", {} }, { "display-name", {} }
}),
createCommand("join-conference-as", QT_TR_NOOP("joinConferenceAsFunctionDescription"), cliJoinConferenceAs, {
{ "sip-address", {} }, { "conference-id", {} }, { "guest-sip-address", {} }
}),
createCommand("bye", QT_TR_NOOP("byeFunctionDescription"), cliBye, QHash<QString, Argument>(), true),
createCommand("accept", QT_TR_NOOP("acceptFunctionDescription"), cliAccept, QHash<QString, Argument>(), true),
createCommand("decline", QT_TR_NOOP("declineFunctionDescription"), cliDecline, QHash<QString, Argument>(), true),
*/
};
std::pair<QString, CliModel::Command> CliModel::createCommand(const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme,
const bool &genericArguments) {
return {functionName.toLower(),
CliModel::Command(functionName.toLower(), functionDescription, function, argsScheme, genericArguments)};
}
CliModel::CliModel(QObject *parent) : QObject(parent) {
moveToThread(CoreModel::getInstance()->thread());
}
CliModel::~CliModel() {
}
std::shared_ptr<CliModel> CliModel::create(QObject *parent) {
auto model = std::make_shared<CliModel>(parent);
// model->setSelf(model);
return model;
}
std::shared_ptr<CliModel> CliModel::getInstance() {
if (!gCliModel) gCliModel = CliModel::create(nullptr);
return gCliModel;
}
// FIXME: Do not accept args without value like: cmd toto.
// In the future `toto` could be a boolean argument.
QRegularExpression
CliModel::mRegExpArgs("(?:(?:([\\w-]+)\\s*)=\\s*(?:\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|([^\\s]+)\\s*))");
QRegularExpression CliModel::mRegExpFunctionName("^\\s*([a-z-]+)\\s*");
QString CliModel::parseFunctionName(const QString &command, bool isOptional) {
QRegularExpressionMatch match = mRegExpFunctionName.match(command.toLower());
// mRegExpFunctionName.indexIn(command.toLower());
// if (mRegExpFunctionName.pos(1) == -1) {
if (!match.hasMatch()) {
if (!isOptional) qWarning() << QStringLiteral("Unable to parse function name of command: `%1`.").arg(command);
return QString("");
}
// const QStringList texts = mRegExpFunctionName.capturedTexts();
const QStringList texts = match.capturedTexts();
const QString functionName = texts[1];
if (!mCommands.contains(functionName)) {
if (!isOptional) qWarning() << QStringLiteral("This command doesn't exist: `%1`.").arg(functionName);
return QString("");
}
return functionName;
}
QHash<QString, QString> CliModel::parseArgs(const QString &command) {
QHash<QString, QString> args;
int pos = 0;
QRegularExpressionMatchIterator it = mRegExpArgs.globalMatch(command);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
if (match.hasMatch()) {
args[match.captured(1)] = (match.captured(2).isEmpty() ? match.captured(3) : match.captured(2));
}
}
return args;
}
void CliModel::cliShow(QHash<QString, QString> args) {
emit showMainWindow();
}
void CliModel::cliFetchConfig(QHash<QString, QString> args) {
if (args.contains("fetch-config")) {
if (CoreModel::getInstance()->getCore()->getGlobalState() != linphone::GlobalState::On)
connect(
CoreModel::getInstance().get(), &CoreModel::globalStateChanged, this,
[this, args]() { cliFetchConfig(args); }, Qt::SingleShotConnection);
else CoreModel::getInstance()->useFetchConfig(args["fetch-config"]);
}
}
void CliModel::cliCall(QHash<QString, QString> args) {
if (args.contains("sip-address")) {
if (!CoreModel::getInstance()->getCore() ||
CoreModel::getInstance()->getCore()->getGlobalState() != linphone::GlobalState::On)
connect(
CoreModel::getInstance().get(), &CoreModel::globalStateChanged, this, [this, args]() { cliCall(args); },
Qt::SingleShotConnection);
else ToolModel::createCall(args["sip-address"]);
}
}
/*
QString CoreModel::getFetchConfig(QCommandLineParser *parser) {
QString filePath = parser->value("fetch-config");
bool error = false;
filePath = getFetchConfig(filePath, &error);
if (error) {
qWarning() << "Remote provisionning cannot be retrieved. Command have beend cleaned";
createParser();
} else if (!filePath.isEmpty())
mParser->process(
cleanParserKeys(mParser, QStringList("fetch-config"))); // Remove this parameter from the parser
return filePath;
}*/
//--------------------------------------------------------------------
CliModel::Command::Command(const QString &functionName,
const char *functionDescription,
CliModel::Function function,
const QHash<QString, CliModel::Argument> &argsScheme,
const bool &genericArguments)
: mFunctionName(functionName), mFunctionDescription(functionDescription), mFunction(function),
mArgsScheme(argsScheme), mGenericArguments(genericArguments) {
}
void CliModel::Command::execute(QHash<QString, QString> &args, CliModel *parent) {
if (!mGenericArguments) { // Check arguments validity.
for (const auto &argName : args.keys()) {
if (!mArgsScheme.contains(argName)) {
qWarning()
<< QStringLiteral("Command with invalid argument: `%1 (%2)`.").arg(mFunctionName).arg(argName);
return;
}
}
}
// Check missing arguments.
for (const auto &argName : mArgsScheme.keys()) {
if (!mArgsScheme[argName].isOptional && (!args.contains(argName) || args[argName].isEmpty())) {
qWarning() << QStringLiteral("Missing argument for command: `%1 (%2)`.").arg(mFunctionName).arg(argName);
return;
}
}
qDebug() << "Execute";
(parent->*mFunction)(args);
/*
// Execute!
App *app = App::getInstance();
if (app->isOpened()) {
qInfo() << QStringLiteral("Execute command:") << args;
(*mFunction)(args);
} else {
Function f = mFunction;
QObject *context = new QObject();
QObject::connect(app, &App::opened, [f, args, context]() mutable {
if (context) {
delete context;
context = nullptr;
qInfo() << QStringLiteral("Execute deferred command:") << args;
QHash<QString, QString> fuckConst = args;
(*f)(fuckConst);
}
});
}
*/
}
void CliModel::Command::executeUri(QString address, QHash<QString, QString> args, CliModel *parent) {
QUrl url(address);
QString query = url.query();
QStringList parameters = query.split('&');
for (int i = 0; i < parameters.size(); ++i) {
QStringList parameter = parameters[i].split('=');
if (parameter[0] != "" && parameter[0] != "method") {
if (parameter.size() > 1) args[parameter[0]] = QByteArray::fromBase64(parameter[1].toUtf8());
else args[parameter[0]] = "";
}
}
args["sip-address"] = address;
parent->addProcess(ProcessCommand(*this, args, 0, parent));
}
// pUrl can be `anytoken?p1=x&p2=y` or `p1=x&p2=y`. It will only use p1 and p2
void CliModel::Command::executeUrl(const QString &pUrl, CliModel *parent) {
QHash<QString, QString> args;
QStringList urlParts = pUrl.split('?');
QString query = (urlParts.size() > 1 ? urlParts[1] : urlParts[0]);
QString authority = (urlParts.size() > 1 && urlParts[0].contains(':') ? urlParts[0].split(':')[1] : "");
QStringList parameters = query.split('&');
for (int i = 0; i < parameters.size(); ++i) {
QStringList parameter = parameters[i].split('=');
if (parameter[0] != "method") {
if (parameter.size() > 1) args[parameter[0]] = QByteArray::fromBase64(parameter[1].toUtf8());
else args[parameter[0]] = "";
}
}
if (!authority.isEmpty()) args["sip-address"] = authority;
parent->addProcess(ProcessCommand(*this, args, 0, parent));
}
QString CliModel::Command::getFunctionSyntax() const {
QString functionSyntax;
functionSyntax += QStringLiteral("\"");
functionSyntax += mFunctionName;
for (auto &argName : mArgsScheme.keys()) {
functionSyntax += QStringLiteral(" ");
functionSyntax += mArgsScheme[argName].isOptional ? QStringLiteral("[") : QStringLiteral("");
functionSyntax += argName;
functionSyntax += QStringLiteral("=<");
switch (mArgsScheme[argName].type) {
case String:
functionSyntax += QStringLiteral("str");
break;
default:
functionSyntax += QStringLiteral("value");
break;
}
functionSyntax += QString(">");
functionSyntax += mArgsScheme[argName].isOptional ? QStringLiteral("]") : QStringLiteral("");
}
functionSyntax += QStringLiteral("\"");
return functionSyntax;
}
//--------------------------------------------------------------------
void CliModel::executeCommand(const QString &command) { //, CommandFormat *format) {
// Detect if command is a CLI by testing commands
const QString &functionName = parseFunctionName(command, false);
const QString configURI = QString(EXECUTABLE_NAME).toLower() + "-config";
if (!functionName.isEmpty()) { // It is a CLI
qInfo() << QStringLiteral("Detecting cli command: `%1`...").arg(command);
QHash<QString, QString> args = parseArgs(command);
QHash<QString, QString> argsToProcess;
for (auto it = args.begin(); it != args.end(); ++it) {
auto subfonction = parseFunctionName(it.key(), true);
if (!subfonction.isEmpty()) {
QHash<QString, QString> arg;
arg[it.key()] = it.value();
addProcess(ProcessCommand(mCommands[it.key()], arg, 1, this));
} else {
argsToProcess[it.key()] = it.value();
}
}
addProcess(ProcessCommand(mCommands[functionName], argsToProcess, 0, this));
// mCommands[functionName].execute(args, this);
// if (format) *format = CliFormat;
} else { // It is a URI
QStringList tempSipAddress = command.split(':');
QString scheme = "sip";
QString transformedCommand; // In order to pass bellesip parsing, set scheme to 'sip:'.
if (tempSipAddress.size() == 1) {
transformedCommand = "sip:" + command;
} else {
scheme = tempSipAddress[0].toLower();
bool ok = false;
for (const QString &validScheme :
{QString("sip"), "sip-" + QString(EXECUTABLE_NAME).toLower(), QString("sips"),
"sips-" + QString(EXECUTABLE_NAME).toLower(), QString(EXECUTABLE_NAME).toLower() + "-sip",
QString(EXECUTABLE_NAME).toLower() + "-sips", QString("tel"), QString("callto"), configURI})
if (scheme == validScheme) ok = true;
if (!ok) {
qWarning()
<< QStringLiteral("Not a valid URI: `%1` Unsupported scheme: `%2`.").arg(command).arg(scheme);
return;
}
tempSipAddress[0] = "sip";
transformedCommand = tempSipAddress.join(':');
}
if (scheme == configURI) {
QHash<QString, QString> args = parseArgs(command);
QString fetchUrl;
if (args.contains("fetch-config")) fetchUrl = QByteArray::fromBase64(args["fetch-config"].toUtf8());
else {
QUrl url(command.mid(configURI.size() + 1)); // Remove 'exec-config:'
if (url.scheme().isEmpty()) url.setScheme("https");
fetchUrl = url.toString();
}
// if (format) *format = CliFormat;
QHash<QString, QString> dummy;
mCommands["show"].execute(dummy, this); // Just open the app.
QHash<QString, QString> arg;
arg["fetch-config"] = fetchUrl;
addProcess(ProcessCommand(mCommands["fetch-config"], arg, 5, this));
} else {
std::shared_ptr<linphone::Address> address;
QString qAddress = transformedCommand;
if (Utils::isUsername(transformedCommand)) {
address = linphone::Factory::get()->createAddress(
Utils::appStringToCoreString(transformedCommand + "@to.remove"));
address->setDomain("");
qAddress = Utils::coreStringToAppString(address->asString());
if (address && qAddress.isEmpty()) qAddress = transformedCommand;
} else
address = linphone::Factory::get()->createAddress(
Utils::appStringToCoreString(transformedCommand)); // Test if command is an address
// if (format) *format = UriFormat;
qInfo() << QStringLiteral("Detecting URI command: `%1`...").arg(command);
QString functionName;
if (address) {
functionName = Utils::coreStringToAppString(address->getHeader("method")).isEmpty()
? QStringLiteral("call")
: Utils::coreStringToAppString(address->getHeader("method"));
} else {
QStringList fields = command.split('?');
if (fields.size() > 1) {
fields = fields[1].split('&');
for (int i = 0; i < fields.size() && functionName.isEmpty(); ++i) {
QStringList data = fields[i].split('=');
if (data[0] == "method" && data.size() > 1) functionName = data[1];
}
if (functionName.isEmpty()) functionName = "call";
}
}
functionName = functionName.toLower();
if (functionName.isEmpty()) {
qWarning() << QStringLiteral("There is no method set in `%1`.").arg(command);
return;
} else if (!mCommands.contains(functionName)) {
qWarning() << QStringLiteral("This command doesn't exist: `%1`.").arg(functionName);
return;
}
QHash<QString, QString> headers;
if (address) {
// TODO: check if there is too much headers.
for (const auto &argName : mCommands[functionName].mArgsScheme.keys()) {
const std::string header = address->getHeader(Utils::appStringToCoreString(argName));
headers[argName] = QByteArray::fromBase64(QByteArray(header.c_str(), int(header.length())));
}
mCommands[functionName].executeUri(qAddress, headers, this);
} else mCommands[functionName].executeUrl(command, this);
}
}
runProcess();
}
void CliModel::addProcess(ProcessCommand process) {
mQueue << process;
std::sort(mQueue.begin(), mQueue.end(),
[](ProcessCommand &a, ProcessCommand &b) { return a.mPriority >= b.mPriority; });
}
void CliModel::runProcess() {
if (mQueue.size() > 0) {
mQueue.first().run();
mQueue.pop_front();
}
}
void CliModel::resetProcesses() {
mQueue.clear();
}

View file

@ -0,0 +1,139 @@
/*
* 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 CLI_MODEL_H_
#define CLI_MODEL_H_
#include <QObject>
#include <QRegularExpression>
#include <QSharedPointer>
#include <QString>
#include <QThread>
#include <linphone++/linphone.hh>
#include "tool/AbstractObject.hpp"
// =============================================================================
class CliModel : public QObject, public AbstractObject {
Q_OBJECT
public:
CliModel(QObject *parent);
~CliModel();
static std::shared_ptr<CliModel> create(QObject *parent);
static std::shared_ptr<CliModel> getInstance();
QString parseFunctionName(const QString &command, bool isOptional);
QHash<QString, QString> parseArgs(const QString &command);
void cliShow(QHash<QString, QString> args);
void cliFetchConfig(QHash<QString, QString> args);
void cliCall(QHash<QString, QString> args);
static QRegularExpression mRegExpArgs;
static QRegularExpression mRegExpFunctionName;
enum ArgumentType { String };
typedef void (CliModel::*Function)(QHash<QString, QString>);
struct Argument {
Argument(ArgumentType type = String, bool isOptional = false) {
this->type = type;
this->isOptional = isOptional;
}
ArgumentType type;
bool isOptional;
};
class Command {
public:
Command() = default;
Command(const Command &command) = default;
Command(const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme,
const bool &genericArguments = false);
void execute(QHash<QString, QString> &args, CliModel *parent);
void executeUri(QString address, QHash<QString, QString> args, CliModel *parent);
void executeUrl(const QString &url, CliModel *parent);
const char *getFunctionDescription() const {
return mFunctionDescription;
}
QString getFunctionSyntax() const;
QHash<QString, Argument> mArgsScheme;
private:
QString mFunctionName;
const char *mFunctionDescription;
Function mFunction = nullptr;
bool mGenericArguments = false; // Used to avoid check on arguments
};
static QMap<QString, Command> mCommands;
class ProcessCommand : public Command {
public:
ProcessCommand(Command command, QHash<QString, QString> args, int priority, CliModel *parent)
: Command(command), mArguments(args), mPriority(priority), mParent(parent) {
}
bool operator<(const ProcessCommand &item) {
return mPriority < item.mPriority;
}
void run() {
execute(mArguments, mParent);
}
int mPriority = 0;
CliModel *mParent;
QHash<QString, QString> mArguments;
};
QList<ProcessCommand> mQueue;
void addProcess(ProcessCommand); // Add and sort
void runProcess();
void resetProcesses();
static std::pair<QString, Command>
createCommand(const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme = QHash<QString, Argument>(),
const bool &genericArguments = false);
enum CommandFormat {
UnknownFormat,
CliFormat,
UriFormat, // Parameters are in base64
UrlFormat
};
void executeCommand(const QString &command);
signals:
void showMainWindow();
private:
static std::shared_ptr<CliModel> gCliModel;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -165,6 +165,57 @@ void CoreModel::setPathAfterStart() {
lInfo() << "[CoreModel] Using RootCa path : " << QString::fromStdString(mCore->getRootCa());
}
//-------------------------------------------------------------------------------
// FETCH CONFIG
//-------------------------------------------------------------------------------
QString CoreModel::getFetchConfig(QString filePath, bool *error) {
*error = false;
if (!filePath.isEmpty()) {
if (QUrl(filePath).isRelative()) { // this is a file path
filePath = Paths::getConfigFilePath(filePath, false);
if (!filePath.isEmpty()) filePath = "file://" + filePath;
}
if (filePath.isEmpty()) {
qWarning() << "Remote provisionning cannot be retrieved. Command have been cleaned";
*error = true;
}
}
return filePath;
}
void CoreModel::useFetchConfig(QString filePath) {
bool error = false;
filePath = getFetchConfig(filePath, &error);
if (!error && !filePath.isEmpty()) {
if (mCore && mCore->getGlobalState() == linphone::GlobalState::On) {
// TODO
// if (mSettings->getAutoApplyProvisioningConfigUriHandlerEnabled()) setFetchConfig(filePath); else
emit requestFetchConfig(filePath);
} else {
connect(
this, &CoreModel::globalStateChanged, this, [filePath, this]() { useFetchConfig(filePath); },
Qt::SingleShotConnection);
}
}
}
bool CoreModel::setFetchConfig(QString filePath) {
bool fetched = false;
qDebug() << "setFetchConfig with " << filePath;
if (!filePath.isEmpty()) {
if (mCore) {
filePath.replace('\\', '/');
fetched = mCore->setProvisioningUri(Utils::appStringToCoreString(filePath)) == 0;
}
}
if (!fetched) {
qWarning() << "Remote provisionning cannot be retrieved. Command have been cleaned";
} else emit requestRestart();
return fetched;
}
//---------------------------------------------------------------------------------------------------------------------------
void CoreModel::onAccountAdded(const std::shared_ptr<linphone::Core> &core,

View file

@ -28,6 +28,7 @@
#include <QTimer>
#include <linphone++/linphone.hh>
#include "model/cli/CliModel.hpp"
#include "model/listener/Listener.hpp"
#include "model/logger/LoggerModel.hpp"
#include "tool/AbstractObject.hpp"
@ -50,6 +51,10 @@ public:
void start();
void setConfigPath(QString path);
QString getFetchConfig(QString filePath, bool *error);
void useFetchConfig(QString filePath);
bool setFetchConfig(QString filePath);
bool mEnd = false;
std::shared_ptr<linphone::Core> mCore;
@ -61,6 +66,8 @@ signals:
void friendRemoved(const std::shared_ptr<linphone::Friend> &f);
void conferenceInfoCreated(const std::shared_ptr<linphone::ConferenceInfo> &confInfo);
void unreadNotificationsChanged();
void requestFetchConfig(QString path);
void requestRestart();
private:
QString mConfigPath;

View file

@ -100,7 +100,23 @@ bool ToolModel::createCall(const QString &sipAddress,
linphone::MediaEncryption mediaEncryption,
QString *errorMessage) {
bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall()
std::shared_ptr<linphone::Core> core = CoreModel::getInstance()->getCore();
if (waitRegistrationForCall) {
std::shared_ptr<linphone::Account> currentAccount = core->getDefaultAccount();
if (!currentAccount || currentAccount->getState() != linphone::RegistrationState::Ok) {
connect(
CoreModel::getInstance().get(), &CoreModel::accountRegistrationStateChanged,
CoreModel::getInstance().get(),
[sipAddress, options, prepareTransfertAddress, headers, mediaEncryption]() {
ToolModel::createCall(sipAddress, options, prepareTransfertAddress, headers, mediaEncryption);
},
Qt::SingleShotConnection);
return false;
}
}
bool localVideoEnabled = options.contains("localVideoEnabled") ? options["localVideoEnabled"].toBool() : false;
std::shared_ptr<linphone::Address> address = interpretUrl(sipAddress);
@ -114,7 +130,7 @@ bool ToolModel::createCall(const QString &sipAddress,
return false;
}
for (auto &account : core->getAccountList()) {
if (account->getContactAddress()->weakEqual(address)) {
if (account->getContactAddress() && account->getContactAddress()->weakEqual(address)) {
*errorMessage = "The calling address is a connected account.";
lDebug() << "[" + QString(gClassName) + "]" + *errorMessage;
return false;

View file

@ -12,6 +12,8 @@ list(APPEND _LINPHONEAPP_SOURCES
tool/providers/ScreenProvider.cpp
tool/native/DesktopTools.hpp
tool/request/RequestDialog.cpp
)
if (APPLE)

View file

@ -1241,6 +1241,12 @@ bool Utils::isLocal(const QString &address) {
App::postModelSync([&isLocal, address]() { isLocal = ToolModel::isLocal(address); });
return isLocal;
}
bool Utils::isUsername(const QString &txt) {
QRegularExpression regex("^(<?sips?:)?[a-zA-Z0-9+_.\\-]+>?$");
QRegularExpressionMatch match = regex.match(txt);
return match.hasMatch(); // true
}
// QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss"));
// bool Utils::isMe(const QString &address) {

View file

@ -111,6 +111,7 @@ public:
static QString generateSavedFilename(const QString &from, const QString &to);
Q_INVOKABLE static bool isMe(const QString &address);
Q_INVOKABLE static bool isLocal(const QString &address);
Q_INVOKABLE static bool isUsername(const QString &txt); // Regex check
static QString getCountryName(const QLocale::Territory &p_country);
static QString getApplicationProduct();

View file

@ -0,0 +1,25 @@
/*
* 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 "RequestDialog.hpp"
RequestDialog::RequestDialog(QString message, QString details, QObject *parent)
: QObject(parent), mMessage(message), mDetails(details) {
}

View file

@ -0,0 +1,42 @@
/*
* 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 REQUEST_DIALOG_H_
#define REQUEST_DIALOG_H_
#include <QDebug>
#include <QObject>
#include <QString>
class RequestDialog : public QObject {
Q_OBJECT
Q_PROPERTY(QString message MEMBER mMessage NOTIFY messageChanged)
Q_PROPERTY(QString details MEMBER mDetails NOTIFY detailsChanged)
public:
RequestDialog(QString message, QString details, QObject *parent = nullptr);
QString mMessage;
QString mDetails;
signals:
void messageChanged();
void detailsChanged();
void result(int data);
};
#endif

View file

@ -28,11 +28,20 @@ Thread::Thread(QObject *parent) : QThread(parent) {
void Thread::run() {
int toExit = false;
mThreadId = new QObject();
while (!toExit) {
int result = exec();
if (result <= 0) toExit = true;
}
}
Thread::~Thread() {
mThreadId->deleteLater();
}
QObject *Thread::getThreadId() {
return mThreadId;
}
bool Thread::isInLinphoneThread() {
return CoreModel::getInstance() && QThread::currentThread() == CoreModel::getInstance()->thread();
}

View file

@ -26,10 +26,14 @@
class Thread : public QThread {
public:
Thread(QObject *parent = nullptr);
virtual ~Thread();
static bool isInLinphoneThread();
static bool mustBeInLinphoneThread(const QString &context);
static bool mustBeInMainThread(const QString &context);
QObject *getThreadId();
virtual void run();
QObject *mThreadId = nullptr;
};
#endif

View file

@ -12,6 +12,22 @@ ApplicationWindow {
id: popupComp
InformationPopup{}
}
Component{
id: confirmPopupComp
Dialog {
property var requestDialog
property int index
signal closePopup(int index)
onClosed: closePopup(index)
text: requestDialog.message
details: requestDialog.details
onAccepted: requestDialog.result(1)
onRejected: requestDialog.result(0)
width: 278 * DefaultStyle.dp
}
}
function removeFromPopupLayout(index) {
popupLayout.popupList.splice(index, 1)
}
@ -30,6 +46,15 @@ ApplicationWindow {
loadingPopup.close()
}
function showConfirmationPopup(requestDialog){
console.log("Showing confirmation popup")
var popup = confirmPopupComp.createObject(popupLayout, {"requestDialog": requestDialog})
popup.index = popupLayout.popupList.length
popupLayout.popupList.push(popup)
popup.open()
popup.closePopup.connect(removeFromPopupLayout)
}
ColumnLayout {
id: popupLayout
anchors.fill: parent
@ -57,4 +82,4 @@ ApplicationWindow {
underlineColor: DefaultStyle.main1_500_main
radius: 15 * DefaultStyle.dp
}
}
}

View file

@ -18,6 +18,7 @@ Popup {
property alias buttons: buttonsLayout.data
property alias content: contentLayout.data
property string text
property string details
signal accepted()
signal rejected()
@ -74,6 +75,21 @@ Popup {
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
Text {
id: detailsText
visible: text.length != 0
width: parent.width
Layout.preferredWidth: 278 * DefaultStyle.dp
Layout.alignment: Qt.AlignCenter
text: mainItem.details
font {
pixelSize: 13 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
italic: true
}
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
RowLayout {
id: buttonsLayout
@ -108,4 +124,4 @@ Popup {
}
}
}
}
}

View file

@ -99,7 +99,8 @@ else
fi
###########################################################################################
export QMAKE=qmake
export QMAKE=${QT_PATH}/bin/qmake
export QML_SOURCES_PATHS=${QML_SOURCES_PATHS}:${WORK_DIR}/..
export LD_LIBRARY_PATH=${QT_PATH}/lib:${BIN_SOURCE_DIR}/lib:${BIN_SOURCE_DIR}/lib64
#export EXTRA_QT_PLUGINS=webenginecore;webview;webengine

View file

@ -7,5 +7,5 @@ Exec=@EXECUTABLE_NAME@ %u
Icon=@EXECUTABLE_NAME@
Terminal=false
Categories=Network;Telephony;
MimeType=x-scheme-handler/sip-@EXECUTABLE_NAME@;x-scheme-handler/sip;x-scheme-handler/sips-@EXECUTABLE_NAME@;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/callto;x-scheme-handler/@EXECUTABLE_NAME@-config;
MimeType=x-scheme-handler/sip-@EXECUTABLE_NAME@;x-scheme-handler/@EXECUTABLE_NAME@-sip;x-scheme-handler/sip;x-scheme-handler/sips-@EXECUTABLE_NAME@;x-scheme-handler/@EXECUTABLE_NAME@-sips;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/callto;x-scheme-handler/@EXECUTABLE_NAME@-config;
X-PulseAudio-Properties=media.role=phone

View file

@ -48,6 +48,8 @@
<string>tel</string>
<string>callto</string>
<string>@EXECUTABLE_NAME@-config</string>
<string>@EXECUTABLE_NAME@-sip</string>
<string>@EXECUTABLE_NAME@-sips</string>
</array>
</dict>
</array>

View file

@ -19,6 +19,9 @@ WriteRegStr HKCR "sip" "URL Protocol" ""
WriteRegStr HKCR "sip-@EXECUTABLE_NAME@" "" "URL:sip-@EXECUTABLE_NAME@ Protocol"
WriteRegStr HKCR "sip-@EXECUTABLE_NAME@" "URL Protocol" ""
WriteRegStr HKCR "@EXECUTABLE_NAME@-sip" "" "URL:@EXECUTABLE_NAME@-sip Protocol"
WriteRegStr HKCR "@EXECUTABLE_NAME@-sip" "URL Protocol" ""
WriteRegStr HKCR "@EXECUTABLE_NAME@-config" "" "URL:@EXECUTABLE_NAME@-config Protocol"
WriteRegStr HKCR "@EXECUTABLE_NAME@-config" "URL Protocol" ""
@ -28,6 +31,9 @@ WriteRegStr HKCR "sips" "URL Protocol" ""
WriteRegStr HKCR "sips-@EXECUTABLE_NAME@" "" "URL:sips-@EXECUTABLE_NAME@ Protocol"
WriteRegStr HKCR "sips-@EXECUTABLE_NAME@" "URL Protocol" ""
WriteRegStr HKCR "@EXECUTABLE_NAME@-sips" "" "URL:@EXECUTABLE_NAME@-sips Protocol"
WriteRegStr HKCR "@EXECUTABLE_NAME@-sips" "URL Protocol" ""
WriteRegStr HKCR "tel" "" "URL:tel Protocol"
WriteRegStr HKCR "tel" "URL Protocol" ""
@ -51,6 +57,13 @@ WriteRegStr HKCR "@APPLICATION_NAME@.sip-@EXECUTABLE_NAME@\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.sip-@EXECUTABLE_NAME@\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "sip-@EXECUTABLE_NAME@" "@APPLICATION_NAME@.sip-@EXECUTABLE_NAME@"
## @EXECUTABLE_NAME@-SIP
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sip" "" "@APPLICATION_NAME@ @EXECUTABLE_NAME@-sip Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sip\Shell" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sip\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sip\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "@EXECUTABLE_NAME@-sip" "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sip"
## SIPS
WriteRegStr HKCR "@APPLICATION_NAME@.sips" "" "@APPLICATION_NAME@ sips Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.sips\Shell" "" ""
@ -65,6 +78,13 @@ WriteRegStr HKCR "@APPLICATION_NAME@.sips-@EXECUTABLE_NAME@\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.sips-@EXECUTABLE_NAME@\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "sips-@EXECUTABLE_NAME@" "@APPLICATION_NAME@.sips-@EXECUTABLE_NAME@"
## @EXECUTABLE_NAME@-SIPS
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sips" "" "@APPLICATION_NAME@ @EXECUTABLE_NAME@-sips Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sips\Shell" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sips\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sips\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "@EXECUTABLE_NAME@-sips" "@APPLICATION_NAME@.@EXECUTABLE_NAME@-sips"
## @EXECUTABLE_NAME@-CONFIG
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config" "" "@APPLICATION_NAME@ @EXECUTABLE_NAME@-config Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config\Shell" "" ""

@ -1 +1 @@
Subproject commit ea3bb6f2284ce16383c3251b9b899dc9b06d0ead
Subproject commit ff8f01d91f4a37773541ef05fb3c29d759116264