Uri Handler + Restart + Remote provisioning + Confirmation dialog with c++ callback behavior
This commit is contained in:
parent
891f9acd8c
commit
4631ea7fe7
24 changed files with 1085 additions and 49 deletions
|
|
@ -28,6 +28,7 @@
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
#include <QQmlComponent>
|
#include <QQmlComponent>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQmlFileSelector>
|
#include <QQmlFileSelector>
|
||||||
|
|
@ -74,14 +75,21 @@
|
||||||
#include "tool/providers/AvatarProvider.hpp"
|
#include "tool/providers/AvatarProvider.hpp"
|
||||||
#include "tool/providers/ImageProvider.hpp"
|
#include "tool/providers/ImageProvider.hpp"
|
||||||
#include "tool/providers/ScreenProvider.hpp"
|
#include "tool/providers/ScreenProvider.hpp"
|
||||||
|
#include "tool/request/RequestDialog.hpp"
|
||||||
#include "tool/thread/Thread.hpp"
|
#include "tool/thread/Thread.hpp"
|
||||||
|
|
||||||
DEFINE_ABSTRACT_OBJECT(App)
|
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[])
|
App::App(int &argc, char *argv[])
|
||||||
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
|
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
|
||||||
// Do not use APPLICATION_NAME here.
|
// Do not use APPLICATION_NAME here.
|
||||||
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
|
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
|
||||||
|
|
||||||
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
|
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
|
||||||
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
|
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
|
||||||
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
|
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
|
||||||
|
|
@ -101,6 +109,7 @@ App::App(int &argc, char *argv[])
|
||||||
|
|
||||||
//-------------------
|
//-------------------
|
||||||
mLinphoneThread = new Thread(this);
|
mLinphoneThread = new Thread(this);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
lInfo() << QStringLiteral("Starting application " APPLICATION_NAME " (bin: " EXECUTABLE_NAME
|
lInfo() << QStringLiteral("Starting application " APPLICATION_NAME " (bin: " EXECUTABLE_NAME
|
||||||
"). Version:%1 Os:%2 Qt:%3")
|
"). Version:%1 Os:%2 Qt:%3")
|
||||||
|
|
@ -126,6 +135,50 @@ void App::setSelf(QSharedPointer<App>(me)) {
|
||||||
lDebug() << "App : call created" << callGui;
|
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() {
|
App *App::getInstance() {
|
||||||
|
|
@ -140,12 +193,33 @@ Notifier *App::getNotifier() const {
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
|
|
||||||
void App::init() {
|
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.
|
// Core. Manage the logger so it must be instantiate at first.
|
||||||
auto coreModel = CoreModel::create("", mLinphoneThread);
|
CoreModel::create("", mLinphoneThread);
|
||||||
connect(
|
QMetaObject::invokeMethod(
|
||||||
mLinphoneThread, &QThread::started, coreModel.get(),
|
mLinphoneThread->getThreadId(),
|
||||||
[this, coreModel]() mutable {
|
[this]() mutable {
|
||||||
coreModel->start();
|
CoreModel::getInstance()->start();
|
||||||
auto settings = Settings::create();
|
auto settings = Settings::create();
|
||||||
QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings]() mutable {
|
QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings]() mutable {
|
||||||
// QML
|
// QML
|
||||||
|
|
@ -161,29 +235,27 @@ void App::init() {
|
||||||
mEngine->addImportPath(":/");
|
mEngine->addImportPath(":/");
|
||||||
mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath());
|
mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath());
|
||||||
#ifdef APPLICATION_VENDOR
|
#ifdef APPLICATION_VENDOR
|
||||||
mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR);
|
mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR);
|
||||||
#else
|
#else
|
||||||
mEngine->rootContext()->setContextProperty("applicationVendor", "");
|
mEngine->rootContext()->setContextProperty("applicationVendor", "");
|
||||||
#endif
|
#endif
|
||||||
#ifdef APPLICATION_LICENCE
|
#ifdef APPLICATION_LICENCE
|
||||||
mEngine->rootContext()->setContextProperty("applicationLicence", APPLICATION_LICENCE);
|
mEngine->rootContext()->setContextProperty("applicationLicence", APPLICATION_LICENCE);
|
||||||
#else
|
#else
|
||||||
mEngine->rootContext()->setContextProperty("applicationLicence", "");
|
mEngine->rootContext()->setContextProperty("applicationLicence", "");
|
||||||
#endif
|
#endif
|
||||||
#ifdef APPLICATION_LICENCE_URL
|
#ifdef APPLICATION_LICENCE_URL
|
||||||
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", APPLICATION_LICENCE_URL);
|
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", APPLICATION_LICENCE_URL);
|
||||||
#else
|
#else
|
||||||
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", "");
|
mEngine->rootContext()->setContextProperty("applicationLicenceUrl", "");
|
||||||
#endif
|
#endif
|
||||||
#ifdef COPYRIGHT_RANGE_DATE
|
#ifdef COPYRIGHT_RANGE_DATE
|
||||||
mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE);
|
mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE);
|
||||||
#else
|
#else
|
||||||
mEngine->rootContext()->setContextProperty("copyrightRangeDate", "");
|
mEngine->rootContext()->setContextProperty("copyrightRangeDate", "");
|
||||||
#endif
|
#endif
|
||||||
mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME);
|
mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME);
|
||||||
mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME);
|
mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME);
|
||||||
|
|
||||||
|
|
||||||
initCppInterfaces();
|
initCppInterfaces();
|
||||||
mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider());
|
mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider());
|
||||||
mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider());
|
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");
|
lCritical() << log().arg("Main.qml couldn't be load. The app will exit");
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
mMainWindow = qobject_cast<QQuickWindow *>(obj);
|
setMainWindow(qobject_cast<QQuickWindow *>(obj));
|
||||||
QMetaObject::invokeMethod(obj, "initStackViewItem");
|
QMetaObject::invokeMethod(obj, "initStackViewItem");
|
||||||
Q_ASSERT(mMainWindow);
|
Q_ASSERT(mMainWindow);
|
||||||
}
|
}
|
||||||
|
|
@ -215,27 +287,7 @@ void App::init() {
|
||||||
});
|
});
|
||||||
// coreModel.reset();
|
// coreModel.reset();
|
||||||
},
|
},
|
||||||
Qt::SingleShotConnection);
|
Qt::BlockingQueuedConnection);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::initCppInterfaces() {
|
void App::initCppInterfaces() {
|
||||||
|
|
@ -294,6 +346,9 @@ void App::initCppInterfaces() {
|
||||||
QLatin1String("Uncreatable"));
|
QLatin1String("Uncreatable"));
|
||||||
qmlRegisterType<VideoSourceDescriptorGui>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui");
|
qmlRegisterType<VideoSourceDescriptorGui>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui");
|
||||||
|
|
||||||
|
qmlRegisterUncreatableType<RequestDialog>(Constants::MainQmlUri, 1, 0, "RequestDialog",
|
||||||
|
QLatin1String("Uncreatable"));
|
||||||
|
|
||||||
LinphoneEnums::registerMetaTypes();
|
LinphoneEnums::registerMetaTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,11 +364,29 @@ void App::clean() {
|
||||||
// mSettings.reset();
|
// mSettings.reset();
|
||||||
// }
|
// }
|
||||||
qApp->processEvents(QEventLoop::AllEvents, 500);
|
qApp->processEvents(QEventLoop::AllEvents, 500);
|
||||||
mLinphoneThread->exit();
|
if (mLinphoneThread) {
|
||||||
mLinphoneThread->wait();
|
mLinphoneThread->exit();
|
||||||
delete mLinphoneThread;
|
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() {
|
void App::createCommandParser() {
|
||||||
if (!mParser) delete mParser;
|
if (!mParser) delete mParser;
|
||||||
|
|
||||||
|
|
@ -336,6 +409,28 @@ void App::createCommandParser() {
|
||||||
{"qt-logs-only", tr("commandLineOptionQtLogsOnly")},
|
{"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 App::notify(QObject *receiver, QEvent *event) {
|
||||||
bool done = true;
|
bool done = true;
|
||||||
|
|
@ -401,6 +496,122 @@ void App::closeCallsWindow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickWindow *App::getMainWindow() {
|
QQuickWindow *App::getMainWindow() const {
|
||||||
return mMainWindow;
|
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
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include "core/setting/SettingsCore.hpp"
|
#include "core/setting/SettingsCore.hpp"
|
||||||
#include "core/singleapplication/singleapplication.h"
|
#include "core/singleapplication/singleapplication.h"
|
||||||
|
#include "model/cli/CliModel.hpp"
|
||||||
#include "model/core/CoreModel.hpp"
|
#include "model/core/CoreModel.hpp"
|
||||||
#include "tool/AbstractObject.hpp"
|
#include "tool/AbstractObject.hpp"
|
||||||
|
|
||||||
|
|
@ -33,6 +34,7 @@ class Notifier;
|
||||||
class QQuickWindow;
|
class QQuickWindow;
|
||||||
|
|
||||||
class App : public SingleApplication, public AbstractObject {
|
class App : public SingleApplication, public AbstractObject {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
App(int &argc, char *argv[]);
|
App(int &argc, char *argv[]);
|
||||||
~App();
|
~App();
|
||||||
|
|
@ -98,21 +100,38 @@ public:
|
||||||
|
|
||||||
void clean();
|
void clean();
|
||||||
void init();
|
void init();
|
||||||
|
void initCore();
|
||||||
void initCppInterfaces();
|
void initCppInterfaces();
|
||||||
|
void restart();
|
||||||
|
|
||||||
void onLoggerInitialized();
|
void onLoggerInitialized();
|
||||||
|
void sendCommand();
|
||||||
|
|
||||||
QQuickWindow *getCallsWindow(QVariant callGui = QVariant());
|
QQuickWindow *getCallsWindow(QVariant callGui = QVariant());
|
||||||
void setCallsWindowProperty(const char *id, QVariant property);
|
void setCallsWindowProperty(const char *id, QVariant property);
|
||||||
void closeCallsWindow();
|
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;
|
QQmlApplicationEngine *mEngine = nullptr;
|
||||||
bool notify(QObject *receiver, QEvent *event) override;
|
bool notify(QObject *receiver, QEvent *event) override;
|
||||||
|
|
||||||
enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 };
|
enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 };
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void mainWindowChanged();
|
||||||
|
// void executeCommand(QString command);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createCommandParser();
|
void createCommandParser();
|
||||||
|
|
||||||
|
|
@ -123,6 +142,7 @@ private:
|
||||||
QQuickWindow *mCallsWindow = nullptr;
|
QQuickWindow *mCallsWindow = nullptr;
|
||||||
QSharedPointer<Settings> mSettings;
|
QSharedPointer<Settings> mSettings;
|
||||||
QSharedPointer<SafeConnection<App, CoreModel>> mCoreModelConnection;
|
QSharedPointer<SafeConnection<App, CoreModel>> mCoreModelConnection;
|
||||||
|
QSharedPointer<SafeConnection<App, CliModel>> mCliModelConnection;
|
||||||
|
|
||||||
DECLARE_ABSTRACT_OBJECT
|
DECLARE_ABSTRACT_OBJECT
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(int msecs, const QByteArray &
|
||||||
socket->write(msg);
|
socket->write(msg);
|
||||||
socket->flush();
|
socket->flush();
|
||||||
|
|
||||||
bool result = socket->waitForReadyRead(msecs); // await ack byte
|
bool result = socket->waitForReadyRead( msecs < 0 ? -1 : msecs); // await ack byte
|
||||||
if (result) {
|
if (result) {
|
||||||
socket->read(1);
|
socket->read(1);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@
|
||||||
#include <QQmlDebuggingEnabler>
|
#include <QQmlDebuggingEnabler>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
FILE *gStream = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 10)
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 10)
|
||||||
// From 5.15.2 to 5.15.10, sometimes, Accessibility freeze the application : Deactivate handlers.
|
// From 5.15.2 to 5.15.10, sometimes, Accessibility freeze the application : Deactivate handlers.
|
||||||
#define ACCESSBILITY_WORKAROUND
|
#define ACCESSBILITY_WORKAROUND
|
||||||
|
|
@ -22,14 +27,30 @@ void DummyRootObjectHandler(QObject *) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void cleanStream() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (gStream) {
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
fclose(gStream);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
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
|
// Useful to share camera on Fullscreen (other context) or multiscreens
|
||||||
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
// Disable QML cache. Avoid malformed cache.
|
// Disable QML cache. Avoid malformed cache.
|
||||||
qputenv("QML_DISABLE_DISK_CACHE", "true");
|
qputenv("QML_DISABLE_DISK_CACHE", "true");
|
||||||
|
|
||||||
auto app = QSharedPointer<App>::create(argc, argv);
|
auto app = QSharedPointer<App>::create(argc, argv);
|
||||||
app->setSelf(app);
|
|
||||||
|
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||||
|
|
@ -46,8 +67,20 @@ int main(int argc, char *argv[]) {
|
||||||
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
|
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
|
||||||
#endif
|
#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;
|
int result = 0;
|
||||||
do {
|
do {
|
||||||
|
app->sendCommand();
|
||||||
result = app->exec();
|
result = app->exec();
|
||||||
} while (result == (int)App::StatusCode::gRestartCode);
|
} while (result == (int)App::StatusCode::gRestartCode);
|
||||||
qWarning() << "[Main] Exiting app with the code : " << result;
|
qWarning() << "[Main] Exiting app with the code : " << result;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
||||||
model/participant/ParticipantDeviceModel.cpp
|
model/participant/ParticipantDeviceModel.cpp
|
||||||
model/participant/ParticipantModel.cpp
|
model/participant/ParticipantModel.cpp
|
||||||
|
|
||||||
|
model/cli/CliModel.cpp
|
||||||
model/core/CoreModel.cpp
|
model/core/CoreModel.cpp
|
||||||
|
|
||||||
model/friend/FriendModel.cpp
|
model/friend/FriendModel.cpp
|
||||||
|
|
|
||||||
405
Linphone/model/cli/CliModel.cpp
Normal file
405
Linphone/model/cli/CliModel.cpp
Normal 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();
|
||||||
|
}
|
||||||
139
Linphone/model/cli/CliModel.hpp
Normal file
139
Linphone/model/cli/CliModel.hpp
Normal 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
|
||||||
|
|
@ -165,6 +165,57 @@ void CoreModel::setPathAfterStart() {
|
||||||
lInfo() << "[CoreModel] Using RootCa path : " << QString::fromStdString(mCore->getRootCa());
|
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,
|
void CoreModel::onAccountAdded(const std::shared_ptr<linphone::Core> &core,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <linphone++/linphone.hh>
|
#include <linphone++/linphone.hh>
|
||||||
|
|
||||||
|
#include "model/cli/CliModel.hpp"
|
||||||
#include "model/listener/Listener.hpp"
|
#include "model/listener/Listener.hpp"
|
||||||
#include "model/logger/LoggerModel.hpp"
|
#include "model/logger/LoggerModel.hpp"
|
||||||
#include "tool/AbstractObject.hpp"
|
#include "tool/AbstractObject.hpp"
|
||||||
|
|
@ -50,6 +51,10 @@ public:
|
||||||
void start();
|
void start();
|
||||||
void setConfigPath(QString path);
|
void setConfigPath(QString path);
|
||||||
|
|
||||||
|
QString getFetchConfig(QString filePath, bool *error);
|
||||||
|
void useFetchConfig(QString filePath);
|
||||||
|
bool setFetchConfig(QString filePath);
|
||||||
|
|
||||||
bool mEnd = false;
|
bool mEnd = false;
|
||||||
|
|
||||||
std::shared_ptr<linphone::Core> mCore;
|
std::shared_ptr<linphone::Core> mCore;
|
||||||
|
|
@ -61,6 +66,8 @@ signals:
|
||||||
void friendRemoved(const std::shared_ptr<linphone::Friend> &f);
|
void friendRemoved(const std::shared_ptr<linphone::Friend> &f);
|
||||||
void conferenceInfoCreated(const std::shared_ptr<linphone::ConferenceInfo> &confInfo);
|
void conferenceInfoCreated(const std::shared_ptr<linphone::ConferenceInfo> &confInfo);
|
||||||
void unreadNotificationsChanged();
|
void unreadNotificationsChanged();
|
||||||
|
void requestFetchConfig(QString path);
|
||||||
|
void requestRestart();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString mConfigPath;
|
QString mConfigPath;
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,23 @@ bool ToolModel::createCall(const QString &sipAddress,
|
||||||
linphone::MediaEncryption mediaEncryption,
|
linphone::MediaEncryption mediaEncryption,
|
||||||
QString *errorMessage) {
|
QString *errorMessage) {
|
||||||
bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall()
|
bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall()
|
||||||
|
|
||||||
std::shared_ptr<linphone::Core> core = CoreModel::getInstance()->getCore();
|
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;
|
bool localVideoEnabled = options.contains("localVideoEnabled") ? options["localVideoEnabled"].toBool() : false;
|
||||||
|
|
||||||
std::shared_ptr<linphone::Address> address = interpretUrl(sipAddress);
|
std::shared_ptr<linphone::Address> address = interpretUrl(sipAddress);
|
||||||
|
|
@ -114,7 +130,7 @@ bool ToolModel::createCall(const QString &sipAddress,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (auto &account : core->getAccountList()) {
|
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.";
|
*errorMessage = "The calling address is a connected account.";
|
||||||
lDebug() << "[" + QString(gClassName) + "]" + *errorMessage;
|
lDebug() << "[" + QString(gClassName) + "]" + *errorMessage;
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ list(APPEND _LINPHONEAPP_SOURCES
|
||||||
tool/providers/ScreenProvider.cpp
|
tool/providers/ScreenProvider.cpp
|
||||||
|
|
||||||
tool/native/DesktopTools.hpp
|
tool/native/DesktopTools.hpp
|
||||||
|
|
||||||
|
tool/request/RequestDialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|
|
||||||
|
|
@ -1241,6 +1241,12 @@ bool Utils::isLocal(const QString &address) {
|
||||||
App::postModelSync([&isLocal, address]() { isLocal = ToolModel::isLocal(address); });
|
App::postModelSync([&isLocal, address]() { isLocal = ToolModel::isLocal(address); });
|
||||||
return isLocal;
|
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"));
|
// QDateTime dateTime(QDateTime::fromString(date, "yyyy-MM-dd hh:mm:ss"));
|
||||||
|
|
||||||
// bool Utils::isMe(const QString &address) {
|
// bool Utils::isMe(const QString &address) {
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ public:
|
||||||
static QString generateSavedFilename(const QString &from, const QString &to);
|
static QString generateSavedFilename(const QString &from, const QString &to);
|
||||||
Q_INVOKABLE static bool isMe(const QString &address);
|
Q_INVOKABLE static bool isMe(const QString &address);
|
||||||
Q_INVOKABLE static bool isLocal(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 getCountryName(const QLocale::Territory &p_country);
|
||||||
|
|
||||||
static QString getApplicationProduct();
|
static QString getApplicationProduct();
|
||||||
|
|
|
||||||
25
Linphone/tool/request/RequestDialog.cpp
Normal file
25
Linphone/tool/request/RequestDialog.cpp
Normal 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) {
|
||||||
|
}
|
||||||
42
Linphone/tool/request/RequestDialog.hpp
Normal file
42
Linphone/tool/request/RequestDialog.hpp
Normal 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
|
||||||
|
|
@ -28,11 +28,20 @@ Thread::Thread(QObject *parent) : QThread(parent) {
|
||||||
|
|
||||||
void Thread::run() {
|
void Thread::run() {
|
||||||
int toExit = false;
|
int toExit = false;
|
||||||
|
mThreadId = new QObject();
|
||||||
while (!toExit) {
|
while (!toExit) {
|
||||||
int result = exec();
|
int result = exec();
|
||||||
if (result <= 0) toExit = true;
|
if (result <= 0) toExit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Thread::~Thread() {
|
||||||
|
mThreadId->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject *Thread::getThreadId() {
|
||||||
|
return mThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
bool Thread::isInLinphoneThread() {
|
bool Thread::isInLinphoneThread() {
|
||||||
return CoreModel::getInstance() && QThread::currentThread() == CoreModel::getInstance()->thread();
|
return CoreModel::getInstance() && QThread::currentThread() == CoreModel::getInstance()->thread();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,14 @@
|
||||||
class Thread : public QThread {
|
class Thread : public QThread {
|
||||||
public:
|
public:
|
||||||
Thread(QObject *parent = nullptr);
|
Thread(QObject *parent = nullptr);
|
||||||
|
virtual ~Thread();
|
||||||
static bool isInLinphoneThread();
|
static bool isInLinphoneThread();
|
||||||
static bool mustBeInLinphoneThread(const QString &context);
|
static bool mustBeInLinphoneThread(const QString &context);
|
||||||
static bool mustBeInMainThread(const QString &context);
|
static bool mustBeInMainThread(const QString &context);
|
||||||
|
|
||||||
|
QObject *getThreadId();
|
||||||
|
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
QObject *mThreadId = nullptr;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,22 @@ ApplicationWindow {
|
||||||
id: popupComp
|
id: popupComp
|
||||||
InformationPopup{}
|
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) {
|
function removeFromPopupLayout(index) {
|
||||||
popupLayout.popupList.splice(index, 1)
|
popupLayout.popupList.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +46,15 @@ ApplicationWindow {
|
||||||
loadingPopup.close()
|
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 {
|
ColumnLayout {
|
||||||
id: popupLayout
|
id: popupLayout
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -57,4 +82,4 @@ ApplicationWindow {
|
||||||
underlineColor: DefaultStyle.main1_500_main
|
underlineColor: DefaultStyle.main1_500_main
|
||||||
radius: 15 * DefaultStyle.dp
|
radius: 15 * DefaultStyle.dp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ Popup {
|
||||||
property alias buttons: buttonsLayout.data
|
property alias buttons: buttonsLayout.data
|
||||||
property alias content: contentLayout.data
|
property alias content: contentLayout.data
|
||||||
property string text
|
property string text
|
||||||
|
property string details
|
||||||
signal accepted()
|
signal accepted()
|
||||||
signal rejected()
|
signal rejected()
|
||||||
|
|
||||||
|
|
@ -74,6 +75,21 @@ Popup {
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
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 {
|
RowLayout {
|
||||||
id: buttonsLayout
|
id: buttonsLayout
|
||||||
|
|
@ -108,4 +124,4 @@ Popup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,8 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###########################################################################################
|
###########################################################################################
|
||||||
export QMAKE=qmake
|
|
||||||
|
export QMAKE=${QT_PATH}/bin/qmake
|
||||||
export QML_SOURCES_PATHS=${QML_SOURCES_PATHS}:${WORK_DIR}/..
|
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 LD_LIBRARY_PATH=${QT_PATH}/lib:${BIN_SOURCE_DIR}/lib:${BIN_SOURCE_DIR}/lib64
|
||||||
#export EXTRA_QT_PLUGINS=webenginecore;webview;webengine
|
#export EXTRA_QT_PLUGINS=webenginecore;webview;webengine
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ Exec=@EXECUTABLE_NAME@ %u
|
||||||
Icon=@EXECUTABLE_NAME@
|
Icon=@EXECUTABLE_NAME@
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Network;Telephony;
|
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
|
X-PulseAudio-Properties=media.role=phone
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@
|
||||||
<string>tel</string>
|
<string>tel</string>
|
||||||
<string>callto</string>
|
<string>callto</string>
|
||||||
<string>@EXECUTABLE_NAME@-config</string>
|
<string>@EXECUTABLE_NAME@-config</string>
|
||||||
|
<string>@EXECUTABLE_NAME@-sip</string>
|
||||||
|
<string>@EXECUTABLE_NAME@-sips</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
|
|
||||||
|
|
@ -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:sip-@EXECUTABLE_NAME@ Protocol"
|
||||||
WriteRegStr HKCR "sip-@EXECUTABLE_NAME@" "URL 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:@EXECUTABLE_NAME@-config Protocol"
|
||||||
WriteRegStr HKCR "@EXECUTABLE_NAME@-config" "URL 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:sips-@EXECUTABLE_NAME@ Protocol"
|
||||||
WriteRegStr HKCR "sips-@EXECUTABLE_NAME@" "URL 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:tel Protocol"
|
||||||
WriteRegStr HKCR "tel" "URL 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 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@"
|
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
|
## SIPS
|
||||||
WriteRegStr HKCR "@APPLICATION_NAME@.sips" "" "@APPLICATION_NAME@ sips Protocol"
|
WriteRegStr HKCR "@APPLICATION_NAME@.sips" "" "@APPLICATION_NAME@ sips Protocol"
|
||||||
WriteRegStr HKCR "@APPLICATION_NAME@.sips\Shell" "" ""
|
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 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@"
|
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
|
## @EXECUTABLE_NAME@-CONFIG
|
||||||
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config" "" "@APPLICATION_NAME@ @EXECUTABLE_NAME@-config Protocol"
|
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config" "" "@APPLICATION_NAME@ @EXECUTABLE_NAME@-config Protocol"
|
||||||
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config\Shell" "" ""
|
WriteRegStr HKCR "@APPLICATION_NAME@.@EXECUTABLE_NAME@-config\Shell" "" ""
|
||||||
|
|
|
||||||
2
external/linphone-sdk
vendored
2
external/linphone-sdk
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit ea3bb6f2284ce16383c3251b9b899dc9b06d0ead
|
Subproject commit ff8f01d91f4a37773541ef05fb3c29d759116264
|
||||||
Loading…
Reference in a new issue