mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
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 <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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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());
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// 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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/providers/ScreenProvider.cpp
|
||||
|
||||
tool/native/DesktopTools.hpp
|
||||
|
||||
tool/request/RequestDialog.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
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() {
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" "" ""
|
||||
|
|
|
|||
2
external/linphone-sdk
vendored
2
external/linphone-sdk
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit ea3bb6f2284ce16383c3251b9b899dc9b06d0ead
|
||||
Subproject commit ff8f01d91f4a37773541ef05fb3c29d759116264
|
||||
Loading…
Add table
Reference in a new issue