mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-04-17 20:08:28 +00:00
Windows system notifications
This commit is contained in:
parent
a6974b9e90
commit
57c1a82546
23 changed files with 1306 additions and 181 deletions
|
|
@ -228,7 +228,10 @@ foreach(T ${QT_PACKAGES})
|
||||||
target_link_libraries(${TARGET_NAME} PRIVATE Qt6::${T})
|
target_link_libraries(${TARGET_NAME} PRIVATE Qt6::${T})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(${TARGET_NAME} PRIVATE runtimeobject)
|
||||||
|
target_link_libraries(${TARGET_NAME} PRIVATE shlwapi propsys shell32)
|
||||||
|
endif()
|
||||||
|
|
||||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,10 @@
|
||||||
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
|
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
|
||||||
#endif // if defined(Q_OS_MACOS)
|
#endif // if defined(Q_OS_MACOS)
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#include "core/notifier/WindowsNotificationBackend.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
DEFINE_ABSTRACT_OBJECT(App)
|
DEFINE_ABSTRACT_OBJECT(App)
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
|
|
@ -614,6 +618,10 @@ int App::getEventCount() const {
|
||||||
return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0;
|
return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationBackend *App::getNotificationBackend() const {
|
||||||
|
return mNotificationBackend;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// Initializations
|
// Initializations
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
|
|
@ -744,6 +752,8 @@ void App::initCore() {
|
||||||
mEngine->setObjectOwnership(settings.get(), QQmlEngine::CppOwnership);
|
mEngine->setObjectOwnership(settings.get(), QQmlEngine::CppOwnership);
|
||||||
mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||||
|
|
||||||
|
mNotificationBackend = new NotificationBackend(this);
|
||||||
|
|
||||||
auto initLists = [this] {
|
auto initLists = [this] {
|
||||||
if (mCoreStarted) {
|
if (mCoreStarted) {
|
||||||
if (!mAccountList) setAccountList(AccountList::create());
|
if (!mAccountList) setAccountList(AccountList::create());
|
||||||
|
|
@ -1086,7 +1096,6 @@ void App::clean() {
|
||||||
mDateUpdateTimer.stop();
|
mDateUpdateTimer.stop();
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
removeNativeEventFilter(mLockEventFilter);
|
removeNativeEventFilter(mLockEventFilter);
|
||||||
delete mLockEventFilter;
|
|
||||||
#endif
|
#endif
|
||||||
if (mEngine) {
|
if (mEngine) {
|
||||||
mEngine->clearComponentCache();
|
mEngine->clearComponentCache();
|
||||||
|
|
@ -1698,8 +1707,9 @@ void App::setSysTrayIcon() {
|
||||||
|
|
||||||
//
|
//
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (!mLockEventFilter) mLockEventFilter = new LockEventFilter();
|
if (!mLockEventFilter) mLockEventFilter = new LockEventFilter(this);
|
||||||
connect(mLockEventFilter, &LockEventFilter::sessionUnlocked, this, [this] { emit sessionUnlocked(); });
|
connect(mLockEventFilter, &LockEventFilter::sessionLockedChanged, this,
|
||||||
|
[this](bool locked) { setSessionLocked(locked); });
|
||||||
installNativeEventFilter(mLockEventFilter);
|
installNativeEventFilter(mLockEventFilter);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -1807,6 +1817,20 @@ void App::setScreenRatio(float ratio) {
|
||||||
mScreenRatio = ratio;
|
mScreenRatio = ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
void App::setSessionLocked(bool locked) {
|
||||||
|
if (mSessionLocked != locked) {
|
||||||
|
mSessionLocked = locked;
|
||||||
|
emit sessionLockedChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool App::getSessionLocked() const {
|
||||||
|
return mSessionLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
QAction *App::createMarkAsReadAction(QQuickWindow *window) {
|
QAction *App::createMarkAsReadAction(QQuickWindow *window) {
|
||||||
QAction *markAllReadAction = new QAction(tr("mark_all_read_action"), window);
|
QAction *markAllReadAction = new QAction(tr("mark_all_read_action"), window);
|
||||||
window->connect(markAllReadAction, &QAction::triggered, this, [this] {
|
window->connect(markAllReadAction, &QAction::triggered, this, [this] {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class Notifier;
|
||||||
class QQuickWindow;
|
class QQuickWindow;
|
||||||
class QSystemTrayIcon;
|
class QSystemTrayIcon;
|
||||||
class DefaultTranslatorCore;
|
class DefaultTranslatorCore;
|
||||||
|
class NotificationBackend;
|
||||||
|
|
||||||
class App : public SingleApplication, public AbstractObject {
|
class App : public SingleApplication, public AbstractObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -197,11 +198,18 @@ public:
|
||||||
QString getSdkVersion();
|
QString getSdkVersion();
|
||||||
QString getQtVersion() const;
|
QString getQtVersion() const;
|
||||||
|
|
||||||
|
NotificationBackend *getNotificationBackend() const;
|
||||||
|
|
||||||
Q_INVOKABLE void checkForUpdate(bool requestedByUser = false);
|
Q_INVOKABLE void checkForUpdate(bool requestedByUser = false);
|
||||||
|
|
||||||
float getScreenRatio() const;
|
float getScreenRatio() const;
|
||||||
Q_INVOKABLE void setScreenRatio(float ratio);
|
Q_INVOKABLE void setScreenRatio(float ratio);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
void setSessionLocked(bool locked);
|
||||||
|
bool getSessionLocked() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
Q_INVOKABLE void exportDesktopFile();
|
Q_INVOKABLE void exportDesktopFile();
|
||||||
|
|
||||||
|
|
@ -233,7 +241,7 @@ signals:
|
||||||
void remainingTimeBeforeOidcTimeoutChanged();
|
void remainingTimeBeforeOidcTimeoutChanged();
|
||||||
void currentAccountChanged();
|
void currentAccountChanged();
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
void sessionUnlocked();
|
void sessionLockedChanged();
|
||||||
#endif
|
#endif
|
||||||
// void executeCommand(QString command);
|
// void executeCommand(QString command);
|
||||||
|
|
||||||
|
|
@ -248,6 +256,7 @@ private:
|
||||||
Thread *mLinphoneThread = nullptr;
|
Thread *mLinphoneThread = nullptr;
|
||||||
Notifier *mNotifier = nullptr;
|
Notifier *mNotifier = nullptr;
|
||||||
EventCountNotifier *mEventCountNotifier = nullptr;
|
EventCountNotifier *mEventCountNotifier = nullptr;
|
||||||
|
NotificationBackend *mNotificationBackend = nullptr;
|
||||||
QSystemTrayIcon *mSystemTrayIcon = nullptr;
|
QSystemTrayIcon *mSystemTrayIcon = nullptr;
|
||||||
QQuickWindow *mMainWindow = nullptr;
|
QQuickWindow *mMainWindow = nullptr;
|
||||||
QQuickWindow *mCallsWindow = nullptr;
|
QQuickWindow *mCallsWindow = nullptr;
|
||||||
|
|
@ -278,6 +287,8 @@ private:
|
||||||
float mScreenRatio = 1;
|
float mScreenRatio = 1;
|
||||||
QTimer mOIDCRefreshTimer;
|
QTimer mOIDCRefreshTimer;
|
||||||
int mRemainingTimeBeforeOidcTimeout = 0;
|
int mRemainingTimeBeforeOidcTimeout = 0;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
bool mSessionLocked = false;
|
||||||
|
#endif
|
||||||
DECLARE_ABSTRACT_OBJECT
|
DECLARE_ABSTRACT_OBJECT
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
||||||
core/logger/QtLogger.cpp
|
core/logger/QtLogger.cpp
|
||||||
core/login/LoginPage.cpp
|
core/login/LoginPage.cpp
|
||||||
core/notifier/Notifier.cpp
|
core/notifier/Notifier.cpp
|
||||||
|
core/notifier/AbstractNotificationBackend.cpp
|
||||||
core/path/Paths.cpp
|
core/path/Paths.cpp
|
||||||
core/phone-number/PhoneNumber.cpp
|
core/phone-number/PhoneNumber.cpp
|
||||||
core/phone-number/PhoneNumberList.cpp
|
core/phone-number/PhoneNumberList.cpp
|
||||||
|
|
@ -129,6 +130,12 @@ else() # Use QDBus for Linux
|
||||||
core/singleapplication/SingleApplicationDBusPrivate.hpp
|
core/singleapplication/SingleApplicationDBusPrivate.hpp
|
||||||
core/singleapplication/SingleApplicationDBus.cpp)
|
core/singleapplication/SingleApplicationDBus.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
if(WIN32)
|
||||||
|
list(APPEND _LINPHONEAPP_SOURCES
|
||||||
|
core/notifier/NotificationActivator.cpp
|
||||||
|
core/notifier/DesktopNotificationManagerCompat.cpp
|
||||||
|
core/notifier/WindowsNotificationBackend.cpp)
|
||||||
|
endif()
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierMacOs.m)
|
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierMacOs.m)
|
||||||
else()
|
else()
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
|
||||||
auto remoteAddress = call->getCallLog()->getRemoteAddress();
|
auto remoteAddress = call->getCallLog()->getRemoteAddress();
|
||||||
mRemoteAddress = Utils::coreStringToAppString(remoteAddress->asStringUriOnly());
|
mRemoteAddress = Utils::coreStringToAppString(remoteAddress->asStringUriOnly());
|
||||||
mRemoteUsername = Utils::coreStringToAppString(remoteAddress->getUsername());
|
mRemoteUsername = Utils::coreStringToAppString(remoteAddress->getUsername());
|
||||||
|
mCallId = Utils::coreStringToAppString(call->getCallLog()->getCallId());
|
||||||
auto linphoneFriend = ToolModel::findFriendByAddress(remoteAddress);
|
auto linphoneFriend = ToolModel::findFriendByAddress(remoteAddress);
|
||||||
if (linphoneFriend)
|
if (linphoneFriend)
|
||||||
mRemoteName = Utils::coreStringToAppString(
|
mRemoteName = Utils::coreStringToAppString(
|
||||||
|
|
@ -520,6 +521,10 @@ QString CallCore::getLocalAddress() const {
|
||||||
return mLocalAddress;
|
return mLocalAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CallCore::getCallId() const {
|
||||||
|
return mCallId;
|
||||||
|
}
|
||||||
|
|
||||||
LinphoneEnums::CallStatus CallCore::getStatus() const {
|
LinphoneEnums::CallStatus CallCore::getStatus() const {
|
||||||
return mStatus;
|
return mStatus;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,8 @@ public:
|
||||||
QString getRemoteAddress() const;
|
QString getRemoteAddress() const;
|
||||||
QString getLocalAddress() const;
|
QString getLocalAddress() const;
|
||||||
|
|
||||||
|
QString getCallId() const;
|
||||||
|
|
||||||
LinphoneEnums::CallStatus getStatus() const;
|
LinphoneEnums::CallStatus getStatus() const;
|
||||||
void setStatus(LinphoneEnums::CallStatus status);
|
void setStatus(LinphoneEnums::CallStatus status);
|
||||||
|
|
||||||
|
|
@ -334,6 +336,7 @@ private:
|
||||||
QString mRemoteUsername;
|
QString mRemoteUsername;
|
||||||
QString mRemoteAddress;
|
QString mRemoteAddress;
|
||||||
QString mLocalAddress;
|
QString mLocalAddress;
|
||||||
|
QString mCallId;
|
||||||
bool mTokenVerified = false;
|
bool mTokenVerified = false;
|
||||||
bool mIsSecured = false;
|
bool mIsSecured = false;
|
||||||
bool mIsMismatch = false;
|
bool mIsMismatch = false;
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,8 @@ bool LockEventFilter::nativeEventFilter(const QByteArray &eventType, void *messa
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
MSG *msg = static_cast<MSG *>(message);
|
MSG *msg = static_cast<MSG *>(message);
|
||||||
if (msg->message == WM_WTSSESSION_CHANGE) {
|
if (msg->message == WM_WTSSESSION_CHANGE) {
|
||||||
if (msg->wParam == WTS_SESSION_LOCK) {
|
emit sessionLockedChanged(msg->wParam == WTS_SESSION_LOCK);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
emit sessionUnlocked();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public:
|
||||||
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
|
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sessionUnlocked();
|
void sessionLockedChanged(bool locked);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LOCKEVENTFILTER_H
|
#endif // LOCKEVENTFILTER_H
|
||||||
|
|
|
||||||
10
Linphone/core/notifier/AbstractNotificationBackend.cpp
Normal file
10
Linphone/core/notifier/AbstractNotificationBackend.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#include "AbstractNotificationBackend.hpp"
|
||||||
|
|
||||||
|
DEFINE_ABSTRACT_OBJECT(AbstractNotificationBackend)
|
||||||
|
|
||||||
|
const QHash<int, AbstractNotificationBackend::Notification> AbstractNotificationBackend::Notifications = {
|
||||||
|
{AbstractNotificationBackend::ReceivedMessage, Notification(AbstractNotificationBackend::ReceivedMessage, 10)},
|
||||||
|
{AbstractNotificationBackend::ReceivedCall, Notification(AbstractNotificationBackend::ReceivedCall, 30)}};
|
||||||
|
|
||||||
|
AbstractNotificationBackend::AbstractNotificationBackend(QObject *parent) : QObject(parent) {
|
||||||
|
}
|
||||||
60
Linphone/core/notifier/AbstractNotificationBackend.hpp
Normal file
60
Linphone/core/notifier/AbstractNotificationBackend.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#ifndef ABSTRACTNOTIFICATIONBACKEND_HPP
|
||||||
|
#define ABSTRACTNOTIFICATIONBACKEND_HPP
|
||||||
|
|
||||||
|
#include "tool/AbstractObject.hpp"
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct ToastButton {
|
||||||
|
QString label;
|
||||||
|
QString argument;
|
||||||
|
ToastButton(QString title, QString arg) {
|
||||||
|
label = title;
|
||||||
|
argument = arg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AbstractNotificationBackend : public QObject, public AbstractObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AbstractNotificationBackend(QObject *parent = Q_NULLPTR);
|
||||||
|
~AbstractNotificationBackend() = default;
|
||||||
|
|
||||||
|
enum NotificationType {
|
||||||
|
ReceivedMessage,
|
||||||
|
ReceivedCall
|
||||||
|
// ReceivedFileMessage,
|
||||||
|
// SnapshotWasTaken,
|
||||||
|
// RecordingCompleted
|
||||||
|
};
|
||||||
|
struct Notification {
|
||||||
|
Notification(int type, int timeout = 0) {
|
||||||
|
this->type = NotificationType(type);
|
||||||
|
this->timeout = timeout;
|
||||||
|
}
|
||||||
|
int getTimeout() const {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int type;
|
||||||
|
int timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void sendNotification(const QString &title = QString(),
|
||||||
|
const QString &message = QString(),
|
||||||
|
const QList<ToastButton> &actions = {}) = 0;
|
||||||
|
|
||||||
|
virtual void sendNotification(NotificationType type, QVariantMap data) = 0;
|
||||||
|
static const QHash<int, Notification> Notifications;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void toastActivated(const QString &args);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DECLARE_ABSTRACT_OBJECT
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ABSTRACTNOTIFICATIONBACKEND_HPP
|
||||||
376
Linphone/core/notifier/DesktopNotificationManagerCompat.cpp
Normal file
376
Linphone/core/notifier/DesktopNotificationManagerCompat.cpp
Normal file
|
|
@ -0,0 +1,376 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// This code is licensed under the MIT License (MIT).
|
||||||
|
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||||
|
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
|
||||||
|
// ******************************************************************
|
||||||
|
#include <shlobj.h>
|
||||||
|
|
||||||
|
#include "DesktopNotificationManagerCompat.hpp"
|
||||||
|
#include "NotificationActivator.hpp"
|
||||||
|
#include <NotificationActivationCallback.h>
|
||||||
|
#include <windows.ui.notifications.h>
|
||||||
|
|
||||||
|
#include <appmodel.h>
|
||||||
|
#include <wrl\wrappers\corewrappers.h>
|
||||||
|
|
||||||
|
#include <propkey.h>
|
||||||
|
#include <propvarutil.h>
|
||||||
|
|
||||||
|
#include "core/App.hpp"
|
||||||
|
|
||||||
|
#pragma comment(lib, "propsys.lib")
|
||||||
|
|
||||||
|
using namespace ABI::Windows::Data::Xml::Dom;
|
||||||
|
using namespace ABI::Windows::UI::Notifications;
|
||||||
|
using namespace Microsoft::WRL;
|
||||||
|
|
||||||
|
#define RETURN_IF_FAILED(hr) \
|
||||||
|
do { \
|
||||||
|
HRESULT _hrTemp = hr; \
|
||||||
|
if (FAILED(_hrTemp)) { \
|
||||||
|
return _hrTemp; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
using namespace ABI::Windows::Data::Xml::Dom;
|
||||||
|
using namespace Microsoft::WRL::Wrappers;
|
||||||
|
|
||||||
|
namespace DesktopNotificationManagerCompat {
|
||||||
|
HRESULT RegisterComServer(GUID clsid, const wchar_t exePath[]);
|
||||||
|
HRESULT RegisterAumidInRegistry(const wchar_t *aumid);
|
||||||
|
HRESULT EnsureRegistered();
|
||||||
|
bool IsRunningAsUwp();
|
||||||
|
|
||||||
|
bool s_registeredAumidAndComServer = false;
|
||||||
|
std::wstring s_aumid;
|
||||||
|
bool s_registeredActivator = false;
|
||||||
|
bool s_hasCheckedIsRunningAsUwp = false;
|
||||||
|
bool s_isRunningAsUwp = false;
|
||||||
|
|
||||||
|
DWORD g_comCookie = 0;
|
||||||
|
|
||||||
|
HRESULT CreateStartMenuShortcut(const wchar_t *aumid, GUID clsid) {
|
||||||
|
// Chemin de destination du raccourci
|
||||||
|
wchar_t shortcutPath[MAX_PATH];
|
||||||
|
SHGetFolderPathW(nullptr, CSIDL_PROGRAMS, nullptr, 0, shortcutPath);
|
||||||
|
wcsncat_s(shortcutPath, L"\\" APPLICATION_NAME L".lnk", MAX_PATH);
|
||||||
|
|
||||||
|
// Créer le IShellLink
|
||||||
|
ComPtr<IShellLinkW> shellLink;
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
|
||||||
|
// Pointer vers l'exe courant
|
||||||
|
wchar_t exePath[MAX_PATH];
|
||||||
|
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||||
|
qDebug() << "EXE path for shortcut:" << QString::fromWCharArray(exePath);
|
||||||
|
shellLink->SetPath(exePath);
|
||||||
|
shellLink->SetArguments(L"");
|
||||||
|
|
||||||
|
// Définir les propriétés AUMID + ToastActivatorCLSID
|
||||||
|
ComPtr<IPropertyStore> propStore;
|
||||||
|
hr = shellLink.As(&propStore);
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
|
||||||
|
PROPVARIANT pv;
|
||||||
|
|
||||||
|
// AUMID
|
||||||
|
InitPropVariantFromString(aumid, &pv);
|
||||||
|
propStore->SetValue(PKEY_AppUserModel_ID, pv);
|
||||||
|
PropVariantClear(&pv);
|
||||||
|
|
||||||
|
// Toast Activator CLSID
|
||||||
|
InitPropVariantFromCLSID(clsid, &pv);
|
||||||
|
propStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, pv);
|
||||||
|
PropVariantClear(&pv);
|
||||||
|
|
||||||
|
propStore->Commit();
|
||||||
|
|
||||||
|
// Sauvegarder le fichier .lnk
|
||||||
|
ComPtr<IPersistFile> persistFile;
|
||||||
|
hr = shellLink.As(&persistFile);
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
|
||||||
|
return persistFile->Save(shortcutPath, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT RegisterAumidInRegistry(const wchar_t *aumid) {
|
||||||
|
std::wstring keyPath = std::wstring(L"Software\\Classes\\AppUserModelId\\") + aumid;
|
||||||
|
|
||||||
|
HKEY key;
|
||||||
|
LONG res = ::RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE,
|
||||||
|
nullptr, &key, nullptr);
|
||||||
|
|
||||||
|
if (res != ERROR_SUCCESS) return HRESULT_FROM_WIN32(res);
|
||||||
|
|
||||||
|
// DisplayName obligatoire pour que Windows affiche la notification
|
||||||
|
const wchar_t *displayName = aumid;
|
||||||
|
res = ::RegSetValueExW(key, L"DisplayName", 0, REG_SZ, reinterpret_cast<const BYTE *>(displayName),
|
||||||
|
static_cast<DWORD>((wcslen(displayName) + 1) * sizeof(wchar_t)));
|
||||||
|
|
||||||
|
::RegCloseKey(key);
|
||||||
|
return HRESULT_FROM_WIN32(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid) {
|
||||||
|
// If running as Desktop Bridge
|
||||||
|
qDebug() << QString("CLSID : {%1-%2-%3-%4%5-%6%7%8%9%10%11}")
|
||||||
|
.arg(clsid.Data1, 8, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data2, 4, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data3, 4, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[0], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[1], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[2], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[3], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[4], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[5], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[6], 2, 16, QChar('0'))
|
||||||
|
.toUpper()
|
||||||
|
.arg(clsid.Data4[7], 2, 16, QChar('0'))
|
||||||
|
.toUpper();
|
||||||
|
if (IsRunningAsUwp()) {
|
||||||
|
// Clear the AUMID since Desktop Bridge doesn't use it, and then we're done.
|
||||||
|
// Desktop Bridge apps are registered with platform through their manifest.
|
||||||
|
// Their LocalServer32 key is also registered through their manifest.
|
||||||
|
qInfo() << "clear AUMID as it is not needed";
|
||||||
|
|
||||||
|
s_aumid = L"";
|
||||||
|
s_registeredAumidAndComServer = true;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the aumid
|
||||||
|
s_aumid = std::wstring(aumid);
|
||||||
|
qDebug() << "S_AUMID:" << s_aumid;
|
||||||
|
|
||||||
|
// Get the EXE path
|
||||||
|
wchar_t exePath[MAX_PATH];
|
||||||
|
DWORD charWritten = ::GetModuleFileName(nullptr, exePath, ARRAYSIZE(exePath));
|
||||||
|
RETURN_IF_FAILED(charWritten > 0 ? S_OK : HRESULT_FROM_WIN32(::GetLastError()));
|
||||||
|
|
||||||
|
// Register the COM server
|
||||||
|
qInfo() << "Register com server and aumid";
|
||||||
|
RETURN_IF_FAILED(RegisterComServer(clsid, exePath));
|
||||||
|
|
||||||
|
qInfo() << "Register aumid in registry";
|
||||||
|
RETURN_IF_FAILED(RegisterAumidInRegistry(aumid));
|
||||||
|
|
||||||
|
s_registeredAumidAndComServer = true;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT RegisterActivator() {
|
||||||
|
// Module<OutOfProc> needs a callback registered before it can be used.
|
||||||
|
// Since we don't care about when it shuts down, we'll pass an empty lambda here.
|
||||||
|
Module<OutOfProc>::Create([] {});
|
||||||
|
|
||||||
|
// If a local server process only hosts the COM object then COM expects
|
||||||
|
// the COM server host to shutdown when the references drop to zero.
|
||||||
|
// Since the user might still be using the program after activating the notification,
|
||||||
|
// we don't want to shutdown immediately. Incrementing the object count tells COM that
|
||||||
|
// we aren't done yet.
|
||||||
|
Module<OutOfProc>::GetModule().IncrementObjectCount();
|
||||||
|
|
||||||
|
// HRESULT hr = CoRegisterClassObject(__uuidof(NotificationActivator), factory, CLSCTX_LOCAL_SERVER,
|
||||||
|
// REGCLS_MULTIPLEUSE, &g_comCookie);
|
||||||
|
|
||||||
|
// qInfo() << "CoRegisterClassObject result:" << Qt::hex << hr << "Cookie:" << g_comCookie;
|
||||||
|
|
||||||
|
// factory->Release();
|
||||||
|
|
||||||
|
auto hr = Module<OutOfProc>::GetModule().RegisterObjects();
|
||||||
|
qInfo() << "RegisterObjects result:" << Qt::hex << hr;
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "CoRegisterClassObject ÉCHOUÉ ? Activate() jamais appelé !";
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_registeredActivator = true;
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT RegisterComServer(GUID clsid, const wchar_t exePath[]) {
|
||||||
|
// Turn the GUID into a string
|
||||||
|
OLECHAR *clsidOlechar;
|
||||||
|
StringFromCLSID(clsid, &clsidOlechar);
|
||||||
|
std::wstring clsidStr(clsidOlechar);
|
||||||
|
::CoTaskMemFree(clsidOlechar);
|
||||||
|
|
||||||
|
// Create the subkey
|
||||||
|
// Something like SOFTWARE\Classes\CLSID\{23A5B06E-20BB-4E7E-A0AC-6982ED6A6041}\LocalServer32
|
||||||
|
std::wstring subKey = LR"(SOFTWARE\Classes\CLSID\)" + clsidStr + LR"(\LocalServer32)";
|
||||||
|
|
||||||
|
// Include -ToastActivated launch args on the exe
|
||||||
|
std::wstring exePathStr(exePath);
|
||||||
|
exePathStr = L"\"" + exePathStr + L"\" " + TOAST_ACTIVATED_LAUNCH_ARG;
|
||||||
|
|
||||||
|
// We don't need to worry about overflow here as ::GetModuleFileName won't
|
||||||
|
// return anything bigger than the max file system path (much fewer than max of DWORD).
|
||||||
|
DWORD dataSize = static_cast<DWORD>((exePathStr.length() + 1) * sizeof(WCHAR));
|
||||||
|
|
||||||
|
// Register the EXE for the COM server
|
||||||
|
return HRESULT_FROM_WIN32(::RegSetKeyValue(HKEY_CURRENT_USER, subKey.c_str(), nullptr, REG_SZ,
|
||||||
|
reinterpret_cast<const BYTE *>(exePathStr.c_str()), dataSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CreateToastNotifier(IToastNotifier **notifier) {
|
||||||
|
RETURN_IF_FAILED(EnsureRegistered());
|
||||||
|
|
||||||
|
ComPtr<IToastNotificationManagerStatics> toastStatics;
|
||||||
|
RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory(
|
||||||
|
HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics));
|
||||||
|
|
||||||
|
if (s_aumid.empty()) {
|
||||||
|
return toastStatics->CreateToastNotifier(notifier);
|
||||||
|
} else {
|
||||||
|
return toastStatics->CreateToastNotifierWithId(HStringReference(s_aumid.c_str()).Get(), notifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CreateXmlDocumentFromString(const wchar_t *xmlString, IXmlDocument **doc) {
|
||||||
|
ComPtr<IXmlDocument> answer;
|
||||||
|
RETURN_IF_FAILED(Windows::Foundation::ActivateInstance(
|
||||||
|
HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &answer));
|
||||||
|
|
||||||
|
ComPtr<IXmlDocumentIO> docIO;
|
||||||
|
RETURN_IF_FAILED(answer.As(&docIO));
|
||||||
|
|
||||||
|
// Load the XML string
|
||||||
|
RETURN_IF_FAILED(docIO->LoadXml(HStringReference(xmlString).Get()));
|
||||||
|
|
||||||
|
return answer.CopyTo(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT CreateToastNotification(IXmlDocument *content, IToastNotification **notification) {
|
||||||
|
ComPtr<IToastNotificationFactory> factory;
|
||||||
|
RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory(
|
||||||
|
HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &factory));
|
||||||
|
|
||||||
|
return factory->CreateToastNotification(content, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT get_History(std::unique_ptr<DesktopNotificationHistoryCompat> *history) {
|
||||||
|
RETURN_IF_FAILED(EnsureRegistered());
|
||||||
|
|
||||||
|
ComPtr<IToastNotificationManagerStatics> toastStatics;
|
||||||
|
RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory(
|
||||||
|
HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics));
|
||||||
|
|
||||||
|
ComPtr<IToastNotificationManagerStatics2> toastStatics2;
|
||||||
|
RETURN_IF_FAILED(toastStatics.As(&toastStatics2));
|
||||||
|
|
||||||
|
ComPtr<IToastNotificationHistory> nativeHistory;
|
||||||
|
RETURN_IF_FAILED(toastStatics2->get_History(&nativeHistory));
|
||||||
|
|
||||||
|
*history = std::unique_ptr<DesktopNotificationHistoryCompat>(
|
||||||
|
new DesktopNotificationHistoryCompat(s_aumid.c_str(), nativeHistory));
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanUseHttpImages() {
|
||||||
|
return IsRunningAsUwp();
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT EnsureRegistered() {
|
||||||
|
// If not registered AUMID yet
|
||||||
|
if (!s_registeredAumidAndComServer) {
|
||||||
|
// Check if Desktop Bridge
|
||||||
|
if (IsRunningAsUwp()) {
|
||||||
|
// Implicitly registered, all good!
|
||||||
|
s_registeredAumidAndComServer = true;
|
||||||
|
} else {
|
||||||
|
// Otherwise, incorrect usage, must call RegisterAumidAndComServer first
|
||||||
|
return E_ILLEGAL_METHOD_CALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not registered activator yet
|
||||||
|
if (!s_registeredActivator) {
|
||||||
|
// Incorrect usage, must call RegisterActivator first
|
||||||
|
return E_ILLEGAL_METHOD_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsRunningAsUwp() {
|
||||||
|
if (!s_hasCheckedIsRunningAsUwp) {
|
||||||
|
// https://stackoverflow.com/questions/39609643/determine-if-c-application-is-running-as-a-uwp-app-in-desktop-bridge-project
|
||||||
|
UINT32 length;
|
||||||
|
wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH + 1];
|
||||||
|
LONG result = GetPackageFamilyName(GetCurrentProcess(), &length, packageFamilyName);
|
||||||
|
s_isRunningAsUwp = result == ERROR_SUCCESS;
|
||||||
|
s_hasCheckedIsRunningAsUwp = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_isRunningAsUwp;
|
||||||
|
}
|
||||||
|
} // namespace DesktopNotificationManagerCompat
|
||||||
|
|
||||||
|
DesktopNotificationHistoryCompat::DesktopNotificationHistoryCompat(const wchar_t *aumid,
|
||||||
|
ComPtr<IToastNotificationHistory> history) {
|
||||||
|
m_aumid = std::wstring(aumid);
|
||||||
|
m_history = history;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT DesktopNotificationHistoryCompat::Clear() {
|
||||||
|
if (m_aumid.empty()) {
|
||||||
|
return m_history->Clear();
|
||||||
|
} else {
|
||||||
|
return m_history->ClearWithId(HStringReference(m_aumid.c_str()).Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT DesktopNotificationHistoryCompat::GetHistory(
|
||||||
|
ABI::Windows::Foundation::Collections::IVectorView<ToastNotification *> **toasts) {
|
||||||
|
ComPtr<IToastNotificationHistory2> history2;
|
||||||
|
RETURN_IF_FAILED(m_history.As(&history2));
|
||||||
|
|
||||||
|
if (m_aumid.empty()) {
|
||||||
|
return history2->GetHistory(toasts);
|
||||||
|
} else {
|
||||||
|
return history2->GetHistoryWithId(HStringReference(m_aumid.c_str()).Get(), toasts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT DesktopNotificationHistoryCompat::Remove(const wchar_t *tag) {
|
||||||
|
if (m_aumid.empty()) {
|
||||||
|
return m_history->Remove(HStringReference(tag).Get());
|
||||||
|
} else {
|
||||||
|
return m_history->RemoveGroupedTagWithId(HStringReference(tag).Get(), HStringReference(L"").Get(),
|
||||||
|
HStringReference(m_aumid.c_str()).Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT DesktopNotificationHistoryCompat::RemoveGroupedTag(const wchar_t *tag, const wchar_t *group) {
|
||||||
|
if (m_aumid.empty()) {
|
||||||
|
return m_history->RemoveGroupedTag(HStringReference(tag).Get(), HStringReference(group).Get());
|
||||||
|
} else {
|
||||||
|
return m_history->RemoveGroupedTagWithId(HStringReference(tag).Get(), HStringReference(group).Get(),
|
||||||
|
HStringReference(m_aumid.c_str()).Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT DesktopNotificationHistoryCompat::RemoveGroup(const wchar_t *group) {
|
||||||
|
if (m_aumid.empty()) {
|
||||||
|
return m_history->RemoveGroup(HStringReference(group).Get());
|
||||||
|
} else {
|
||||||
|
return m_history->RemoveGroupWithId(HStringReference(group).Get(), HStringReference(m_aumid.c_str()).Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Linphone/core/notifier/DesktopNotificationManagerCompat.hpp
Normal file
112
Linphone/core/notifier/DesktopNotificationManagerCompat.hpp
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
// ******************************************************************
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// This code is licensed under the MIT License (MIT).
|
||||||
|
// THE CODE IS PROVIDED ?AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||||
|
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
|
||||||
|
// ******************************************************************
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <windows.ui.notifications.h>
|
||||||
|
#include <wrl.h>
|
||||||
|
#define TOAST_ACTIVATED_LAUNCH_ARG L"-ToastActivated"
|
||||||
|
|
||||||
|
using namespace ABI::Windows::UI::Notifications;
|
||||||
|
|
||||||
|
class DesktopNotificationHistoryCompat;
|
||||||
|
|
||||||
|
namespace DesktopNotificationManagerCompat {
|
||||||
|
|
||||||
|
/// Froce shortcut creation
|
||||||
|
HRESULT CreateStartMenuShortcut(const wchar_t *aumid, GUID clsid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If not running under the Desktop Bridge, you must call this method to register your AUMID with the Compat library
|
||||||
|
/// and to register your COM CLSID and EXE in LocalServer32 registry. Feel free to call this regardless, and we will
|
||||||
|
/// no-op if running under Desktop Bridge. Call this upon application startup, before calling any other APIs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aumid">An AUMID that uniquely identifies your application.</param>
|
||||||
|
/// <param name="clsid">The CLSID of your NotificationActivator class.</param>
|
||||||
|
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers your module to handle COM activations. Call this upon application startup.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT RegisterActivator();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a toast notifier. You must have called RegisterActivator first (and also RegisterAumidAndComServer if you're
|
||||||
|
/// a classic Win32 app), or this will throw an exception.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT CreateToastNotifier(IToastNotifier **notifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an XmlDocument initialized with the specified string. This is simply a convenience helper method.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT CreateXmlDocumentFromString(const wchar_t *xmlString, ABI::Windows::Data::Xml::Dom::IXmlDocument **doc);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a toast notification. This is simply a convenience helper method.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT CreateToastNotification(ABI::Windows::Data::Xml::Dom::IXmlDocument *content, IToastNotification **notification);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the DesktopNotificationHistoryCompat object. You must have called RegisterActivator first (and also
|
||||||
|
/// RegisterAumidAndComServer if you're a classic Win32 app), or this will throw an exception.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT get_History(std::unique_ptr<DesktopNotificationHistoryCompat> *history);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean representing whether http images can be used within toasts. This is true if running under Desktop
|
||||||
|
/// Bridge.
|
||||||
|
/// </summary>
|
||||||
|
bool CanUseHttpImages();
|
||||||
|
} // namespace DesktopNotificationManagerCompat
|
||||||
|
|
||||||
|
class DesktopNotificationHistoryCompat {
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all notifications sent by this app from action center.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all notifications sent by this app that are currently still in Action Center.
|
||||||
|
/// </summary>
|
||||||
|
HRESULT GetHistory(ABI::Windows::Foundation::Collections::IVectorView<ToastNotification *> **history);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes an individual toast, with the specified tag label, from action center.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag label of the toast notification to be removed.</param>
|
||||||
|
HRESULT Remove(const wchar_t *tag);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a toast notification from the action using the notification's tag and group labels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag label of the toast notification to be removed.</param>
|
||||||
|
/// <param name="group">The group label of the toast notification to be removed.</param>
|
||||||
|
HRESULT RemoveGroupedTag(const wchar_t *tag, const wchar_t *group);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a group of toast notifications, identified by the specified group label, from action center.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The group label of the toast notifications to be removed.</param>
|
||||||
|
HRESULT RemoveGroup(const wchar_t *group);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do not call this. Instead, call DesktopNotificationManagerCompat.get_History() to obtain an instance.
|
||||||
|
/// </summary>
|
||||||
|
DesktopNotificationHistoryCompat(const wchar_t *aumid, Microsoft::WRL::ComPtr<IToastNotificationHistory> history);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::wstring m_aumid;
|
||||||
|
Microsoft::WRL::ComPtr<IToastNotificationHistory> m_history = nullptr;
|
||||||
|
};
|
||||||
20
Linphone/core/notifier/NotificationActivator.cpp
Normal file
20
Linphone/core/notifier/NotificationActivator.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "NotificationActivator.hpp"
|
||||||
|
|
||||||
|
NotificationActivator::NotificationActivator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationActivator::~NotificationActivator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE NotificationActivator::Activate(LPCWSTR appUserModelId,
|
||||||
|
LPCWSTR invokedArgs,
|
||||||
|
const NOTIFICATION_USER_INPUT_DATA *data,
|
||||||
|
ULONG dataCount) {
|
||||||
|
Q_UNUSED(appUserModelId);
|
||||||
|
Q_UNUSED(invokedArgs);
|
||||||
|
Q_UNUSED(data);
|
||||||
|
Q_UNUSED(dataCount);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoCreatableClass(NotificationActivator);
|
||||||
42
Linphone/core/notifier/NotificationActivator.hpp
Normal file
42
Linphone/core/notifier/NotificationActivator.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef NOTIFICATIONACTIVATOR_HPP
|
||||||
|
#define NOTIFICATIONACTIVATOR_HPP
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <NotificationActivationCallback.h>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wrl/implements.h>
|
||||||
|
#include <wrl/module.h>
|
||||||
|
// #include <wil/com.h>
|
||||||
|
#include <windows.data.xml.dom.h>
|
||||||
|
#include <windows.ui.notifications.h>
|
||||||
|
// #include <winrt/base.h>
|
||||||
|
|
||||||
|
// using namespace winrt;
|
||||||
|
// using namespace Windows::UI::Notifications;
|
||||||
|
// using namespace Windows::Data::Xml::Dom;
|
||||||
|
using namespace ABI::Windows::Data::Xml::Dom;
|
||||||
|
using namespace Microsoft::WRL;
|
||||||
|
|
||||||
|
using Microsoft::WRL::ClassicCom;
|
||||||
|
using Microsoft::WRL::RuntimeClass;
|
||||||
|
using Microsoft::WRL::RuntimeClassFlags;
|
||||||
|
|
||||||
|
class DECLSPEC_UUID("FC946101-E4AB-4EA4-BC2E-C7F4D72B89AC") NotificationActivator
|
||||||
|
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
|
||||||
|
INotificationActivationCallback> {
|
||||||
|
|
||||||
|
public:
|
||||||
|
NotificationActivator();
|
||||||
|
~NotificationActivator();
|
||||||
|
|
||||||
|
static void onActivated(LPCWSTR invokedArgs); // appelé depuis le .cpp
|
||||||
|
HRESULT STDMETHODCALLTYPE Activate(LPCWSTR appUserModelId,
|
||||||
|
LPCWSTR invokedArgs,
|
||||||
|
const NOTIFICATION_USER_INPUT_DATA *data,
|
||||||
|
ULONG dataCount) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NOTIFICATIONACTIVATOR_HPP
|
||||||
|
|
@ -30,15 +30,13 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "Notifier.hpp"
|
#include "Notifier.hpp"
|
||||||
|
#include "WindowsNotificationBackend.hpp"
|
||||||
|
|
||||||
#include "core/App.hpp"
|
#include "core/App.hpp"
|
||||||
#include "core/call/CallGui.hpp"
|
#include "core/call/CallGui.hpp"
|
||||||
#include "core/chat/ChatGui.hpp"
|
#include "core/chat/ChatGui.hpp"
|
||||||
#include "model/tool/ToolModel.hpp"
|
#include "model/tool/ToolModel.hpp"
|
||||||
#include "tool/LinphoneEnums.hpp"
|
|
||||||
#include "tool/accessibility/AccessibilityHelper.hpp"
|
#include "tool/accessibility/AccessibilityHelper.hpp"
|
||||||
#include "tool/providers/AvatarProvider.hpp"
|
|
||||||
#include "tool/providers/ImageProvider.hpp"
|
|
||||||
|
|
||||||
DEFINE_ABSTRACT_OBJECT(Notifier)
|
DEFINE_ABSTRACT_OBJECT(Notifier)
|
||||||
|
|
||||||
|
|
@ -87,9 +85,12 @@ void setProperty(QObject &object, const char *property, const T &value) {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const QHash<int, Notifier::Notification> Notifier::Notifications = {
|
const QHash<int, Notifier::Notification> Notifier::Notifications = {
|
||||||
{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
|
{AbstractNotificationBackend::ReceivedMessage,
|
||||||
//{Notifier::ReceivedFileMessage, {Notifier::ReceivedFileMessage, "NotificationReceivedFileMessage.qml", 10}},
|
{AbstractNotificationBackend::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
|
||||||
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}}
|
//{AbstractNotificationBackend::ReceivedFileMessage, {AbstractNotificationBackend::ReceivedFileMessage,
|
||||||
|
//"NotificationReceivedFileMessage.qml", 10}},
|
||||||
|
{AbstractNotificationBackend::ReceivedCall,
|
||||||
|
{AbstractNotificationBackend::ReceivedCall, "NotificationReceivedCall.qml", 30}}
|
||||||
//{Notifier::NewVersionAvailable, {Notifier::NewVersionAvailable, "NotificationNewVersionAvailable.qml", 30}},
|
//{Notifier::NewVersionAvailable, {Notifier::NewVersionAvailable, "NotificationNewVersionAvailable.qml", 30}},
|
||||||
//{Notifier::SnapshotWasTaken, {Notifier::SnapshotWasTaken, "NotificationSnapshotWasTaken.qml", 10}},
|
//{Notifier::SnapshotWasTaken, {Notifier::SnapshotWasTaken, "NotificationSnapshotWasTaken.qml", 10}},
|
||||||
//{Notifier::RecordingCompleted, {Notifier::RecordingCompleted, "NotificationRecordingCompleted.qml", 10}}
|
//{Notifier::RecordingCompleted, {Notifier::RecordingCompleted, "NotificationRecordingCompleted.qml", 10}}
|
||||||
|
|
@ -98,7 +99,7 @@ const QHash<int, Notifier::Notification> Notifier::Notifications = {
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
Notifier::Notifier(QObject *parent) : QObject(parent) {
|
Notifier::Notifier(QObject *parent) : QObject(parent) {
|
||||||
mustBeInMainThread(getClassName());
|
mustBeInMainThread("Notifier");
|
||||||
const int nComponents = Notifications.size();
|
const int nComponents = Notifications.size();
|
||||||
mComponents.resize(nComponents);
|
mComponents.resize(nComponents);
|
||||||
|
|
||||||
|
|
@ -118,113 +119,123 @@ Notifier::Notifier(QObject *parent) : QObject(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::~Notifier() {
|
Notifier::~Notifier() {
|
||||||
mustBeInMainThread("~" + getClassName());
|
mustBeInMainThread("~Notifier");
|
||||||
delete mMutex;
|
delete mMutex;
|
||||||
|
|
||||||
const int nComponents = Notifications.size();
|
|
||||||
mComponents.clear();
|
mComponents.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap data) {
|
bool Notifier::createNotification(AbstractNotificationBackend::NotificationType type, QVariantMap data) {
|
||||||
mMutex->lock();
|
mMutex->lock();
|
||||||
// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
|
// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
|
||||||
if (mInstancesNumber >= MaxNotificationsNumber) { // Check existing instances.
|
#ifdef Q_OS_WIN
|
||||||
qWarning() << QStringLiteral("Unable to create another notification.");
|
|
||||||
|
auto notifBackend = App::getInstance()->getNotificationBackend();
|
||||||
|
if (!notifBackend) {
|
||||||
|
qWarning() << QStringLiteral("Unable to get notification backend, return.");
|
||||||
mMutex->unlock();
|
mMutex->unlock();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QList<QScreen *> allScreens = QGuiApplication::screens();
|
notifBackend->sendNotification(type, data);
|
||||||
if (allScreens.size() > 0) { // Ensure to have a screen to avoid errors
|
#else
|
||||||
QQuickItem *previousWrapper = nullptr;
|
|
||||||
bool showAsTool = false;
|
if (mInstancesNumber >= MaxNotificationsNumber) { // Check existing instances.
|
||||||
|
qWarning() << QStringLiteral("Unable to create another notification.");
|
||||||
|
QList<QScreen *> allScreens = QGuiApplication::screens();
|
||||||
|
if (allScreens.size() > 0) { // Ensure to have a screen to avoid errors
|
||||||
|
QQuickItem *previousWrapper = nullptr;
|
||||||
|
bool showAsTool = false;
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
for (auto w : QGuiApplication::topLevelWindows()) {
|
for (auto w : QGuiApplication::topLevelWindows()) {
|
||||||
if (w->visibility() == QWindow::FullScreen) {
|
if (w->visibility() == QWindow::FullScreen) {
|
||||||
showAsTool = true;
|
showAsTool = true;
|
||||||
w->raise(); // Used to get focus on Mac (On Mac, A Tool is hidden if the app has not focus and the
|
w->raise(); // Used to get focus on Mac (On Mac, A Tool is hidden if the app has not focus and the
|
||||||
// only way to rid it is to use Widget Attributes(Qt::WA_MacAlwaysShowToolWindow) that is not available)
|
// only way to rid it is to use Widget Attributes(Qt::WA_MacAlwaysShowToolWindow) that is not
|
||||||
|
// available)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (int i = 0; i < allScreens.size(); ++i) {
|
||||||
|
|
||||||
|
++mInstancesNumber;
|
||||||
|
// Use QQuickView to create a visual root object that is
|
||||||
|
// independant from current application Window
|
||||||
|
QScreen *screen = allScreens[i];
|
||||||
|
auto engine = App::getInstance()->mEngine;
|
||||||
|
const QUrl url(QString(NotificationsPath) + Notifier::Notifications[type].filename);
|
||||||
|
QObject::connect(
|
||||||
|
engine, &QQmlApplicationEngine::objectCreated, this,
|
||||||
|
[this, url, screen, engine, type, data, showAsTool](QObject *obj, const QUrl &objUrl) {
|
||||||
|
if (!obj && url == objUrl) {
|
||||||
|
lCritical() << "[App] Notifier.qml couldn't be load.";
|
||||||
|
engine->deleteLater();
|
||||||
|
exit(-1);
|
||||||
|
} else {
|
||||||
|
auto window = qobject_cast<QQuickWindow *>(obj);
|
||||||
|
if (window) {
|
||||||
|
window->setParent(nullptr);
|
||||||
|
window->setProperty(NotificationPropertyData, data);
|
||||||
|
window->setScreen(screen);
|
||||||
|
// Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool
|
||||||
|
// ensure to have the Window on Top and fullscreen independant
|
||||||
|
window->setFlags((showAsTool ? Qt::Tool : Qt::WindowStaysOnTopHint) |
|
||||||
|
Qt::FramelessWindowHint);
|
||||||
|
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
|
||||||
|
window->setFlag(Qt::WindowDoesNotAcceptFocus);
|
||||||
|
#endif
|
||||||
|
// for (auto it = data.begin(); it != data.end(); ++it)
|
||||||
|
// window->setProperty(it.key().toLatin1(), it.value());
|
||||||
|
const int timeout = Notifications[type].getTimeout() * 1000;
|
||||||
|
auto updateNotificationCoordinates = [this, window, screen](int width, int height) {
|
||||||
|
auto screenHeightOffset =
|
||||||
|
screen ? mScreenHeightOffset.value(screen->name()) : 0; // Access optimization
|
||||||
|
QRect availableGeometry = screen->availableGeometry();
|
||||||
|
|
||||||
|
window->setX(availableGeometry.x() +
|
||||||
|
(availableGeometry.width() -
|
||||||
|
width)); //*screen->devicePixelRatio()); when using manual scaler
|
||||||
|
window->setY(availableGeometry.y() + availableGeometry.height() -
|
||||||
|
screenHeightOffset - height);
|
||||||
|
};
|
||||||
|
updateNotificationCoordinates(window->width(), window->height());
|
||||||
|
auto screenHeightOffset =
|
||||||
|
screen ? mScreenHeightOffset.take(screen->name()) : 0; // Access optimization
|
||||||
|
mScreenHeightOffset.insert(screen->name(), screenHeightOffset + window->height());
|
||||||
|
QObject::connect(window, &QQuickWindow::closing, window, [this, window] {
|
||||||
|
qDebug() << "closing notification";
|
||||||
|
deleteNotification(QVariant::fromValue(window));
|
||||||
|
});
|
||||||
|
QObject::connect(window, &QQuickWindow::visibleChanged, window, [this](bool visible) {
|
||||||
|
lInfo() << log().arg("Notification visible changed") << visible;
|
||||||
|
});
|
||||||
|
QObject::connect(window, &QQuickWindow::activeChanged, window, [this, window] {
|
||||||
|
lInfo() << log().arg("Notification active changed") << window->isActive();
|
||||||
|
});
|
||||||
|
QObject::connect(window, &QQuickWindow::visibilityChanged, window,
|
||||||
|
[this](QWindow::Visibility visibility) {
|
||||||
|
lInfo()
|
||||||
|
<< log().arg("Notification visibility changed") << visibility;
|
||||||
|
});
|
||||||
|
QObject::connect(window, &QQuickWindow::widthChanged, window, [this](int width) {
|
||||||
|
lInfo() << log().arg("Notification width changed") << width;
|
||||||
|
});
|
||||||
|
QObject::connect(window, &QQuickWindow::heightChanged, window, [this](int height) {
|
||||||
|
lInfo() << log().arg("Notification height changed") << height;
|
||||||
|
});
|
||||||
|
lInfo() << QStringLiteral("Create notification:") << window;
|
||||||
|
showNotification(window, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||||
|
lDebug() << log().arg("Engine loading notification");
|
||||||
|
engine->load(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
for (int i = 0; i < allScreens.size(); ++i) {
|
|
||||||
|
|
||||||
++mInstancesNumber;
|
|
||||||
// Use QQuickView to create a visual root object that is
|
|
||||||
// independant from current application Window
|
|
||||||
QScreen *screen = allScreens[i];
|
|
||||||
auto engine = App::getInstance()->mEngine;
|
|
||||||
const QUrl url(QString(NotificationsPath) + Notifier::Notifications[type].filename);
|
|
||||||
QObject::connect(
|
|
||||||
engine, &QQmlApplicationEngine::objectCreated, this,
|
|
||||||
[this, url, screen, engine, type, data, showAsTool](QObject *obj, const QUrl &objUrl) {
|
|
||||||
if (!obj && url == objUrl) {
|
|
||||||
lCritical() << "[App] Notifier.qml couldn't be load.";
|
|
||||||
engine->deleteLater();
|
|
||||||
exit(-1);
|
|
||||||
} else {
|
|
||||||
auto window = qobject_cast<QQuickWindow *>(obj);
|
|
||||||
if (window) {
|
|
||||||
window->setParent(nullptr);
|
|
||||||
window->setProperty(NotificationPropertyData, data);
|
|
||||||
window->setScreen(screen);
|
|
||||||
// Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool ensure
|
|
||||||
// to have the Window on Top and fullscreen independant
|
|
||||||
window->setFlags((showAsTool ? Qt::Tool : Qt::WindowStaysOnTopHint) |
|
|
||||||
Qt::FramelessWindowHint);
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
|
|
||||||
window->setFlag(Qt::WindowDoesNotAcceptFocus);
|
|
||||||
#endif
|
|
||||||
// for (auto it = data.begin(); it != data.end(); ++it)
|
|
||||||
// window->setProperty(it.key().toLatin1(), it.value());
|
|
||||||
const int timeout = Notifications[type].getTimeout() * 1000;
|
|
||||||
auto updateNotificationCoordinates = [this, window, screen](int width, int height) {
|
|
||||||
auto screenHeightOffset =
|
|
||||||
screen ? mScreenHeightOffset.value(screen->name()) : 0; // Access optimization
|
|
||||||
QRect availableGeometry = screen->availableGeometry();
|
|
||||||
|
|
||||||
window->setX(availableGeometry.x() +
|
|
||||||
(availableGeometry.width() -
|
|
||||||
width)); //*screen->devicePixelRatio()); when using manual scaler
|
|
||||||
window->setY(availableGeometry.y() + availableGeometry.height() - screenHeightOffset -
|
|
||||||
height);
|
|
||||||
};
|
|
||||||
updateNotificationCoordinates(window->width(), window->height());
|
|
||||||
auto screenHeightOffset =
|
|
||||||
screen ? mScreenHeightOffset.take(screen->name()) : 0; // Access optimization
|
|
||||||
mScreenHeightOffset.insert(screen->name(), screenHeightOffset + window->height());
|
|
||||||
QObject::connect(window, &QQuickWindow::closing, window, [this, window] {
|
|
||||||
qDebug() << "closing notification";
|
|
||||||
deleteNotification(QVariant::fromValue(window));
|
|
||||||
});
|
|
||||||
QObject::connect(window, &QQuickWindow::visibleChanged, window, [this](bool visible) {
|
|
||||||
lInfo() << log().arg("Notification visible changed") << visible;
|
|
||||||
});
|
|
||||||
QObject::connect(window, &QQuickWindow::activeChanged, window, [this, window] {
|
|
||||||
lInfo() << log().arg("Notification active changed") << window->isActive();
|
|
||||||
});
|
|
||||||
QObject::connect(window, &QQuickWindow::visibilityChanged, window,
|
|
||||||
[this](QWindow::Visibility visibility) {
|
|
||||||
lInfo() << log().arg("Notification visibility changed") << visibility;
|
|
||||||
});
|
|
||||||
QObject::connect(window, &QQuickWindow::widthChanged, window, [this](int width) {
|
|
||||||
lInfo() << log().arg("Notification width changed") << width;
|
|
||||||
});
|
|
||||||
QObject::connect(window, &QQuickWindow::heightChanged, window, [this](int height) {
|
|
||||||
lInfo() << log().arg("Notification height changed") << height;
|
|
||||||
});
|
|
||||||
lInfo() << QStringLiteral("Create notification:") << window;
|
|
||||||
showNotification(window, timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
|
||||||
lDebug() << log().arg("Engine loading notification");
|
|
||||||
engine->load(url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
mMutex->unlock();
|
mMutex->unlock();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -237,17 +248,6 @@ void Notifier::showNotification(QQuickWindow *notification, int timeout) {
|
||||||
timer->setInterval(timeout);
|
timer->setInterval(timeout);
|
||||||
timer->setSingleShot(true);
|
timer->setSingleShot(true);
|
||||||
notification->setProperty(NotificationPropertyTimer, QVariant::fromValue(timer));
|
notification->setProperty(NotificationPropertyTimer, QVariant::fromValue(timer));
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
QObject::connect(App::getInstance(), &App::sessionUnlocked, notification, [this, notification] {
|
|
||||||
if (!notification) return;
|
|
||||||
lInfo() << log().arg("Windows : screen unlocked, force raising notification");
|
|
||||||
notification->hide();
|
|
||||||
notification->showNormal();
|
|
||||||
// notification->raise();
|
|
||||||
lInfo() << log().arg("Notification visibility : visible =") << notification->isVisible()
|
|
||||||
<< "visibility =" << notification->visibility();
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
notification->hide();
|
notification->hide();
|
||||||
notification->showNormal();
|
notification->showNormal();
|
||||||
// notification->raise();
|
// notification->raise();
|
||||||
|
|
@ -322,8 +322,8 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
|
||||||
if (account) {
|
if (account) {
|
||||||
auto accountModel = Utils::makeQObject_ptr<AccountModel>(account);
|
auto accountModel = Utils::makeQObject_ptr<AccountModel>(account);
|
||||||
if (!accountModel->getNotificationsAllowed()) {
|
if (!accountModel->getNotificationsAllowed()) {
|
||||||
lInfo() << log().arg(
|
lInfo() << log().arg("Notifications have been disabled for this account - not creating a notification "
|
||||||
"Notifications have been disabled for this account - not creating a notification for incoming call");
|
"for incoming call");
|
||||||
if (accountModel->forwardToVoiceMailInDndPresence()) {
|
if (accountModel->forwardToVoiceMailInDndPresence()) {
|
||||||
lInfo() << log().arg("Transferring call to voicemail");
|
lInfo() << log().arg("Transferring call to voicemail");
|
||||||
auto voicemailAddress = linphone::Factory::get()->createAddress(
|
auto voicemailAddress = linphone::Factory::get()->createAddress(
|
||||||
|
|
@ -353,7 +353,8 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
|
||||||
|
|
||||||
map["displayName"].setValue(displayName);
|
map["displayName"].setValue(displayName);
|
||||||
map["call"].setValue(gui);
|
map["call"].setValue(gui);
|
||||||
CREATE_NOTIFICATION(Notifier::ReceivedCall, map)
|
|
||||||
|
CREATE_NOTIFICATION(AbstractNotificationBackend::ReceivedCall, map)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,7 +395,7 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
||||||
remoteAddress = ToolModel::getDisplayName(remoteAddr);
|
remoteAddress = ToolModel::getDisplayName(remoteAddr);
|
||||||
auto fileContent = message->getFileTransferInformation();
|
auto fileContent = message->getFileTransferInformation();
|
||||||
if (!fileContent) {
|
if (!fileContent) {
|
||||||
for (auto content : message->getContents()) {
|
for (const auto &content : message->getContents()) {
|
||||||
if (content->isText()) txt += content->getUtf8Text().c_str();
|
if (content->isText()) txt += content->getUtf8Text().c_str();
|
||||||
}
|
}
|
||||||
} else if (fileContent->isVoiceRecording())
|
} else if (fileContent->isVoiceRecording())
|
||||||
|
|
@ -466,7 +467,7 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
||||||
map["avatarUri"] = chatCore->getAvatarUri();
|
map["avatarUri"] = chatCore->getAvatarUri();
|
||||||
map["isGroupChat"] = chatCore->isGroupChat();
|
map["isGroupChat"] = chatCore->isGroupChat();
|
||||||
map["chat"] = QVariant::fromValue(chatCore ? new ChatGui(chatCore) : nullptr);
|
map["chat"] = QVariant::fromValue(chatCore ? new ChatGui(chatCore) : nullptr);
|
||||||
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
|
CREATE_NOTIFICATION(AbstractNotificationBackend::ReceivedMessage, map)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@
|
||||||
#include <linphone++/linphone.hh>
|
#include <linphone++/linphone.hh>
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
#include "core/notifier/AbstractNotificationBackend.hpp"
|
||||||
|
|
||||||
class QMutex;
|
class QMutex;
|
||||||
class QQmlComponent;
|
class QQmlComponent;
|
||||||
|
|
||||||
|
|
@ -40,15 +42,6 @@ public:
|
||||||
Notifier(QObject *parent = Q_NULLPTR);
|
Notifier(QObject *parent = Q_NULLPTR);
|
||||||
~Notifier();
|
~Notifier();
|
||||||
|
|
||||||
enum NotificationType {
|
|
||||||
ReceivedMessage,
|
|
||||||
// ReceivedFileMessage,
|
|
||||||
ReceivedCall
|
|
||||||
// NewVersionAvailable,
|
|
||||||
// SnapshotWasTaken,
|
|
||||||
// RecordingCompleted
|
|
||||||
};
|
|
||||||
|
|
||||||
// void notifyReceivedCall(Call *call);
|
// void notifyReceivedCall(Call *call);
|
||||||
void notifyReceivedCall(const std::shared_ptr<linphone::Call> &call); // Call from Linphone
|
void notifyReceivedCall(const std::shared_ptr<linphone::Call> &call); // Call from Linphone
|
||||||
|
|
||||||
|
|
@ -77,7 +70,7 @@ private:
|
||||||
this->timeout = timeout;
|
this->timeout = timeout;
|
||||||
}
|
}
|
||||||
int getTimeout() const {
|
int getTimeout() const {
|
||||||
if (type == Notifier::ReceivedCall) {
|
if (type == AbstractNotificationBackend::ReceivedCall) {
|
||||||
// return CoreManager::getInstance()->getSettingsModel()->getIncomingCallTimeout();
|
// return CoreManager::getInstance()->getSettingsModel()->getIncomingCallTimeout();
|
||||||
return 30;
|
return 30;
|
||||||
} else return timeout;
|
} else return timeout;
|
||||||
|
|
@ -89,7 +82,7 @@ private:
|
||||||
int type;
|
int type;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool createNotification(NotificationType type, QVariantMap data);
|
bool createNotification(AbstractNotificationBackend::NotificationType type, QVariantMap data);
|
||||||
void showNotification(QQuickWindow *notification, int timeout);
|
void showNotification(QQuickWindow *notification, int timeout);
|
||||||
|
|
||||||
QHash<QString, int> mScreenHeightOffset;
|
QHash<QString, int> mScreenHeightOffset;
|
||||||
|
|
|
||||||
361
Linphone/core/notifier/WindowsNotificationBackend.cpp
Normal file
361
Linphone/core/notifier/WindowsNotificationBackend.cpp
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
#include "tool/Utils.hpp"
|
||||||
|
|
||||||
|
// #include "NotificationActivator.hpp"
|
||||||
|
#include "WindowsNotificationBackend.hpp"
|
||||||
|
#include "core/App.hpp"
|
||||||
|
#include "core/call/CallGui.hpp"
|
||||||
|
#include "core/chat/ChatGui.hpp"
|
||||||
|
#include "core/event-filter/LockEventFilter.hpp"
|
||||||
|
#include "tool/Utils.hpp"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "DesktopNotificationManagerCompat.hpp"
|
||||||
|
#include <windows.foundation.h>
|
||||||
|
#include <windows.ui.notifications.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
using namespace Microsoft::WRL;
|
||||||
|
using namespace ABI::Windows::UI::Notifications;
|
||||||
|
using namespace ABI::Windows::Foundation;
|
||||||
|
using namespace Microsoft::WRL::Wrappers;
|
||||||
|
|
||||||
|
NotificationBackend::NotificationBackend(QObject *parent) : AbstractNotificationBackend(parent) {
|
||||||
|
connect(App::getInstance(), &App::sessionLockedChanged, this, [this] {
|
||||||
|
if (!App::getInstance()->getSessionLocked()) {
|
||||||
|
qDebug() << "Session unlocked, flush pending notifications";
|
||||||
|
flushPendingNotifications();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationBackend::flushPendingNotifications() {
|
||||||
|
for (const auto ¬if : mPendingNotifications) {
|
||||||
|
sendNotification(notif.type, notif.data);
|
||||||
|
}
|
||||||
|
mPendingNotifications.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationBackend::sendNotification(const QString &title,
|
||||||
|
const QString &message,
|
||||||
|
const QList<ToastButton> &actions) {
|
||||||
|
IToastNotifier *notifier = nullptr;
|
||||||
|
HRESULT hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier);
|
||||||
|
if (FAILED(hr) || !notifier) {
|
||||||
|
lWarning() << "CreateToastNotifier failed:" << Qt::hex << hr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring wTitle = title.toStdWString();
|
||||||
|
std::wstring wMessage = message.toStdWString();
|
||||||
|
|
||||||
|
std::wstring wActions;
|
||||||
|
if (!actions.isEmpty()) {
|
||||||
|
wActions += L"<actions>";
|
||||||
|
for (const auto &action : actions) {
|
||||||
|
std::wstring wLabel = action.label.toStdWString();
|
||||||
|
std::wstring wArg = action.argument.toStdWString();
|
||||||
|
wActions += L"<action content=\"" + wLabel + L"\" arguments=\"" + wArg + L"\"/>";
|
||||||
|
}
|
||||||
|
wActions += L"</actions>";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring xml = L"<toast>"
|
||||||
|
L" <visual>"
|
||||||
|
L" <binding template=\"ToastGeneric\">"
|
||||||
|
L" <text>" +
|
||||||
|
wTitle +
|
||||||
|
L"</text>"
|
||||||
|
L" <text>" +
|
||||||
|
wMessage +
|
||||||
|
L"</text>"
|
||||||
|
L" </binding>"
|
||||||
|
L" </visual>" +
|
||||||
|
wActions + L"</toast>";
|
||||||
|
|
||||||
|
ABI::Windows::Data::Xml::Dom::IXmlDocument *doc = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(xml.c_str(), &doc);
|
||||||
|
if (FAILED(hr) || !doc) {
|
||||||
|
lWarning() << "CreateXmlDocumentFromString failed:" << Qt::hex << hr;
|
||||||
|
notifier->Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IToastNotification *toast = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
|
||||||
|
if (FAILED(hr) || !toast) {
|
||||||
|
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventRegistrationToken token;
|
||||||
|
toast->add_Activated(Microsoft::WRL::Callback<ITypedEventHandler<ToastNotification *, IInspectable *>>(
|
||||||
|
[this](IToastNotification *sender, IInspectable *args) -> HRESULT {
|
||||||
|
qInfo() << "Toast clicked!";
|
||||||
|
// Cast args en IToastActivatedEventArgs
|
||||||
|
Microsoft::WRL::ComPtr<IToastActivatedEventArgs> activatedArgs;
|
||||||
|
HRESULT hr = args->QueryInterface(IID_PPV_ARGS(&activatedArgs));
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
HSTRING argumentsHString;
|
||||||
|
activatedArgs->get_Arguments(&argumentsHString);
|
||||||
|
|
||||||
|
// Convertir HSTRING en wstring
|
||||||
|
UINT32 length;
|
||||||
|
const wchar_t *rawStr = WindowsGetStringRawBuffer(argumentsHString, &length);
|
||||||
|
std::wstring arguments(rawStr, length);
|
||||||
|
|
||||||
|
qInfo() << "Toast activated with args:" << QString::fromStdWString(arguments);
|
||||||
|
emit toastActivated(QString::fromStdWString(arguments));
|
||||||
|
|
||||||
|
WindowsDeleteString(argumentsHString);
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
})
|
||||||
|
.Get(),
|
||||||
|
&token);
|
||||||
|
|
||||||
|
hr = notifier->Show(toast);
|
||||||
|
lInfo() << "Show toast:" << Qt::hex << hr;
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "Toast Show failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast->Release();
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationBackend::sendMessageNotification(QVariantMap data) {
|
||||||
|
|
||||||
|
IToastNotifier *notifier = nullptr;
|
||||||
|
HRESULT hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier);
|
||||||
|
if (FAILED(hr) || !notifier) {
|
||||||
|
lWarning() << "CreateToastNotifier failed:" << Qt::hex << hr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msgTxt = data["message"].toString().toStdWString();
|
||||||
|
auto remoteAddress = data["remoteAddress"].toString().toStdWString();
|
||||||
|
auto chatRoomName = data["chatRoomName"].toString().toStdWString();
|
||||||
|
auto chatRoomAddress = data["chatRoomAddress"].toString().toStdWString();
|
||||||
|
auto avatarUri = data["avatarUri"].toString().toStdWString();
|
||||||
|
bool isGroup = data["isGroupChat"].toBool();
|
||||||
|
ChatGui *chat = data["chat"].value<ChatGui *>();
|
||||||
|
|
||||||
|
std::wstring xml = L"<toast>"
|
||||||
|
L" <visual>"
|
||||||
|
L" <binding template=\"ToastGeneric\">"
|
||||||
|
L" <text><![CDATA[" +
|
||||||
|
chatRoomName +
|
||||||
|
L"]]></text>"
|
||||||
|
L" <text><![CDATA[" +
|
||||||
|
(isGroup ? remoteAddress : L"") +
|
||||||
|
L"]]></text>"
|
||||||
|
L" <group>"
|
||||||
|
L" <subgroup>"
|
||||||
|
L" <text hint-style=\"body\"><![CDATA[" +
|
||||||
|
msgTxt +
|
||||||
|
L"]]></text>"
|
||||||
|
L" </subgroup>"
|
||||||
|
L" </group>"
|
||||||
|
L" </binding>"
|
||||||
|
L" </visual>"
|
||||||
|
L" <audio silent=\"true\"/>"
|
||||||
|
L"</toast>";
|
||||||
|
|
||||||
|
ABI::Windows::Data::Xml::Dom::IXmlDocument *doc = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(xml.c_str(), &doc);
|
||||||
|
if (FAILED(hr) || !doc) {
|
||||||
|
lWarning() << "CreateXmlDocumentFromString failed:" << Qt::hex << hr;
|
||||||
|
notifier->Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IToastNotification *toast = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
|
||||||
|
if (FAILED(hr) || !toast) {
|
||||||
|
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventRegistrationToken token;
|
||||||
|
toast->add_Activated(Microsoft::WRL::Callback<ITypedEventHandler<ToastNotification *, IInspectable *>>(
|
||||||
|
[this, chat](IToastNotification *sender, IInspectable *args) -> HRESULT {
|
||||||
|
qInfo() << "Message toast clicked!";
|
||||||
|
|
||||||
|
Utils::openChat(chat);
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
})
|
||||||
|
.Get(),
|
||||||
|
&token);
|
||||||
|
|
||||||
|
hr = notifier->Show(toast);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "Toast Show failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast->Release();
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationBackend::sendCallNotification(QVariantMap data) {
|
||||||
|
|
||||||
|
IToastNotifier *notifier = nullptr;
|
||||||
|
HRESULT hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier);
|
||||||
|
if (FAILED(hr) || !notifier) {
|
||||||
|
lWarning() << "CreateToastNotifier failed:" << Qt::hex << hr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto displayName = data["displayName"].toString().toStdWString();
|
||||||
|
CallGui *call = data["call"].value<CallGui *>();
|
||||||
|
int timeout = 2;
|
||||||
|
// AbstractNotificationBackend::Notifications[(int)NotificationType::ReceivedCall].getTimeout();
|
||||||
|
|
||||||
|
// Incoming call
|
||||||
|
auto callDescription = tr("incoming_call").toStdWString();
|
||||||
|
QList<ToastButton> actions;
|
||||||
|
actions.append(ToastButton(tr("accept_button"), "accept"));
|
||||||
|
actions.append(ToastButton(tr("decline_button"), "decline"));
|
||||||
|
std::wstring wActions;
|
||||||
|
if (!actions.isEmpty()) {
|
||||||
|
wActions += L"<actions>";
|
||||||
|
for (const auto &action : actions) {
|
||||||
|
std::wstring wLabel = action.label.toStdWString();
|
||||||
|
std::wstring wArg = action.argument.toStdWString();
|
||||||
|
wActions += L"<action content=\"" + wLabel + L"\" arguments=\"" + wArg + L"\"/>";
|
||||||
|
}
|
||||||
|
wActions += L"</actions>";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring xml = L"<toast scenario=\"reminder\">"
|
||||||
|
L" <visual>"
|
||||||
|
L" <binding template=\"ToastGeneric\">"
|
||||||
|
L" <text hint-style=\"header\">" +
|
||||||
|
displayName +
|
||||||
|
L"</text>"
|
||||||
|
L" <text hint-style=\"body\">" +
|
||||||
|
callDescription +
|
||||||
|
L"</text>"
|
||||||
|
L" </binding>"
|
||||||
|
L" </visual>" +
|
||||||
|
wActions +
|
||||||
|
L" <audio silent=\"true\"/>"
|
||||||
|
L"</toast>";
|
||||||
|
|
||||||
|
ABI::Windows::Data::Xml::Dom::IXmlDocument *doc = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(xml.c_str(), &doc);
|
||||||
|
if (FAILED(hr) || !doc) {
|
||||||
|
lWarning() << "CreateXmlDocumentFromString failed:" << Qt::hex << hr;
|
||||||
|
notifier->Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IToastNotification *toast = nullptr;
|
||||||
|
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
|
||||||
|
if (FAILED(hr) || !toast) {
|
||||||
|
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComPtr<IToastNotification2> toast2;
|
||||||
|
hr = toast->QueryInterface(IID_PPV_ARGS(&toast2));
|
||||||
|
if (FAILED(hr)) qWarning() << "QueryInterface failed";
|
||||||
|
auto callId = call->mCore->getCallId();
|
||||||
|
qDebug() << "put tag to toast" << callId;
|
||||||
|
hr = toast2->put_Tag(HStringReference(reinterpret_cast<const wchar_t *>(callId.utf16())).Get());
|
||||||
|
toast2->put_Group(HStringReference(L"linphone").Get());
|
||||||
|
if (FAILED(hr)) qWarning() << "puting tag on toast failed";
|
||||||
|
|
||||||
|
connect(call->mCore.get(), &CallCore::stateChanged, this, [this, call, notifier, toast] {
|
||||||
|
if (call->mCore->getState() == LinphoneEnums::CallState::End) {
|
||||||
|
qDebug() << "Call ended, remove toast";
|
||||||
|
auto callId = call->mCore->getCallId();
|
||||||
|
call->deleteLater();
|
||||||
|
|
||||||
|
std::unique_ptr<DesktopNotificationHistoryCompat> history;
|
||||||
|
DesktopNotificationManagerCompat::get_History(&history);
|
||||||
|
|
||||||
|
auto hr = history->RemoveGroupedTag(reinterpret_cast<const wchar_t *>(callId.utf16()), L"linphone");
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "removing toast failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EventRegistrationToken token;
|
||||||
|
toast->add_Activated(Microsoft::WRL::Callback<ITypedEventHandler<ToastNotification *, IInspectable *>>(
|
||||||
|
[this, call](IToastNotification *sender, IInspectable *args) -> HRESULT {
|
||||||
|
qInfo() << "Toast clicked!";
|
||||||
|
// Cast args en IToastActivatedEventArgs
|
||||||
|
Microsoft::WRL::ComPtr<IToastActivatedEventArgs> activatedArgs;
|
||||||
|
HRESULT hr = args->QueryInterface(IID_PPV_ARGS(&activatedArgs));
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
HSTRING argumentsHString;
|
||||||
|
activatedArgs->get_Arguments(&argumentsHString);
|
||||||
|
|
||||||
|
// Convertir HSTRING en wstring
|
||||||
|
UINT32 length;
|
||||||
|
const wchar_t *rawStr = WindowsGetStringRawBuffer(argumentsHString, &length);
|
||||||
|
std::wstring arguments(rawStr, length);
|
||||||
|
QString arg = QString::fromStdWString(arguments);
|
||||||
|
qInfo() << "Toast activated with args:" << arg;
|
||||||
|
|
||||||
|
if (arg.compare("accept") == 0) {
|
||||||
|
if (call) {
|
||||||
|
qDebug() << "Accept call";
|
||||||
|
Utils::openCallsWindow(call);
|
||||||
|
call->mCore->lAccept(false);
|
||||||
|
}
|
||||||
|
} else if (arg.compare("decline") == 0) {
|
||||||
|
if (call) {
|
||||||
|
qDebug() << "Decline call";
|
||||||
|
call->mCore->lDecline();
|
||||||
|
}
|
||||||
|
} else if (arg.isEmpty()) {
|
||||||
|
if (call) Utils::openCallsWindow(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsDeleteString(argumentsHString);
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
})
|
||||||
|
.Get(),
|
||||||
|
&token);
|
||||||
|
|
||||||
|
hr = notifier->Show(toast);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "Toast Show failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast->Release();
|
||||||
|
doc->Release();
|
||||||
|
notifier->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationBackend::sendNotification(NotificationType type, QVariantMap data) {
|
||||||
|
if (App::getInstance()->getSessionLocked()) {
|
||||||
|
mPendingNotifications.append({type, data});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case NotificationType::ReceivedCall:
|
||||||
|
sendCallNotification(data);
|
||||||
|
break;
|
||||||
|
case NotificationType::ReceivedMessage:
|
||||||
|
sendMessageNotification(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Linphone/core/notifier/WindowsNotificationBackend.hpp
Normal file
41
Linphone/core/notifier/WindowsNotificationBackend.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#ifndef WINDOWSNOTIFICATIONBACKEND_HPP
|
||||||
|
#define WINDOWSNOTIFICATIONBACKEND_HPP
|
||||||
|
|
||||||
|
#include "AbstractNotificationBackend.hpp"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class NotificationBackend : public AbstractNotificationBackend {
|
||||||
|
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
struct PendingNotification {
|
||||||
|
NotificationType type;
|
||||||
|
QVariantMap data;
|
||||||
|
};
|
||||||
|
|
||||||
|
NotificationBackend(QObject *parent = nullptr);
|
||||||
|
~NotificationBackend() = default;
|
||||||
|
void sendNotification(const QString &title = QString(),
|
||||||
|
const QString &message = QString(),
|
||||||
|
const QList<ToastButton> &actions = {}) override;
|
||||||
|
|
||||||
|
void sendCallNotification(QVariantMap data);
|
||||||
|
void sendMessageNotification(QVariantMap data);
|
||||||
|
// void sendMessageNotification(QVariantMap data);
|
||||||
|
|
||||||
|
void sendNotification(NotificationType type, QVariantMap data) override;
|
||||||
|
|
||||||
|
void flushPendingNotifications();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void toastButtonTriggered(const QString &arg);
|
||||||
|
void sessionLockedChanged(bool locked);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<PendingNotification> mPendingNotifications;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WINDOWSNOTIFICATIONBACKEND_HPP
|
||||||
|
|
@ -1,35 +1,37 @@
|
||||||
#include <QApplication>
|
#include <qloggingcategory.h>
|
||||||
#include <QQmlApplicationEngine>
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
FILE *gStream = NULL;
|
||||||
|
#include <objbase.h> // StringFromCLSID, CoTaskMemFree
|
||||||
|
#include <propkey.h> // PKEY_AppUserModel_ID, PKEY_AppUserModel_ToastActivatorCLSID
|
||||||
|
#include <propvarutil.h> // InitPropVariantFromString, PropVariantClear
|
||||||
|
#include <shlguid.h> // CLSID_ShellLink
|
||||||
|
#include <shobjidl.h> // IShellLinkW, IPropertyStore
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "core/App.hpp"
|
#include "core/App.hpp"
|
||||||
#include "core/logger/QtLogger.hpp"
|
#include "core/logger/QtLogger.hpp"
|
||||||
#include "core/path/Paths.hpp"
|
#include "core/path/Paths.hpp"
|
||||||
|
|
||||||
|
#include "core/notifier/DesktopNotificationManagerCompat.hpp"
|
||||||
|
#include "core/notifier/NotificationActivator.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <iostream>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#ifdef QT_QML_DEBUG
|
#ifdef QT_QML_DEBUG
|
||||||
#include <QQmlDebuggingEnabler>
|
#include <QQmlDebuggingEnabler>
|
||||||
|
#include <QStandardPaths>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#define WIDEN2(x) L##x
|
||||||
#include <Windows.h>
|
#define WIDEN(x) WIDEN2(x)
|
||||||
FILE *gStream = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 10)
|
static const wchar_t *mAumid = WIDEN(APPLICATION_ID);
|
||||||
// From 5.15.2 to 5.15.10, sometimes, Accessibility freeze the application : Deactivate handlers.
|
|
||||||
#define ACCESSBILITY_WORKAROUND
|
|
||||||
#include <QAccessible>
|
|
||||||
#include <QAccessibleEvent>
|
|
||||||
|
|
||||||
void DummyUpdateHandler(QAccessibleEvent *event) {
|
|
||||||
}
|
|
||||||
void DummyRootObjectHandler(QObject *) {
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void cleanStream() {
|
void cleanStream() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -53,6 +55,19 @@ int main(int argc, char *argv[]) {
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// auto hrCom = RoInitialize(RO_INIT_MULTITHREADED);
|
||||||
|
// HRESULT hrCom = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
// qInfo() << "CoInitializeEx result:" << Qt::hex << hrCom;
|
||||||
|
|
||||||
|
qInfo() << "Thread ID:" << GetCurrentThreadId();
|
||||||
|
APTTYPE aptBefore;
|
||||||
|
APTTYPEQUALIFIER qualBefore;
|
||||||
|
CoGetApartmentType(&aptBefore, &qualBefore);
|
||||||
|
qInfo() << "ApartmentType BEFORE CoInitializeEx:" << aptBefore;
|
||||||
|
|
||||||
|
HRESULT hrCom = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
qInfo() << "CoInitializeEx STA result:" << Qt::hex << hrCom;
|
||||||
|
|
||||||
// Useful to share camera on Fullscreen (other context) or multiscreens
|
// Useful to share camera on Fullscreen (other context) or multiscreens
|
||||||
lDebug() << "[Main] Setting ShareOpenGLContexts";
|
lDebug() << "[Main] Setting ShareOpenGLContexts";
|
||||||
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
|
|
@ -67,8 +82,25 @@ int main(int argc, char *argv[]) {
|
||||||
lDebug() << "[Main] Setting application to UTF8";
|
lDebug() << "[Main] Setting application to UTF8";
|
||||||
setlocale(LC_CTYPE, ".UTF8");
|
setlocale(LC_CTYPE, ".UTF8");
|
||||||
lDebug() << "[Main] Creating application";
|
lDebug() << "[Main] Creating application";
|
||||||
|
|
||||||
|
auto hr = DesktopNotificationManagerCompat::CreateStartMenuShortcut(mAumid, __uuidof(NotificationActivator));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "CreateStartMenuShortcut failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register AUMID and COM server (for a packaged app, this is a no-operation)
|
||||||
|
hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"Linphone", __uuidof(NotificationActivator));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "RegisterAumidAndComServer failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
auto app = QSharedPointer<App>::create(argc, argv);
|
auto app = QSharedPointer<App>::create(argc, argv);
|
||||||
|
|
||||||
|
hr = DesktopNotificationManagerCompat::RegisterActivator();
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "RegisterActivator failed:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ACCESSBILITY_WORKAROUND
|
#ifdef ACCESSBILITY_WORKAROUND
|
||||||
QAccessible::installUpdateHandler(DummyUpdateHandler);
|
QAccessible::installUpdateHandler(DummyUpdateHandler);
|
||||||
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
|
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
class AbstractEventCountNotifier;
|
|
||||||
class EventCountNotifier;
|
class EventCountNotifier;
|
||||||
|
|
||||||
class CoreModel : public ::Listener<linphone::Core, linphone::CoreListener>,
|
class CoreModel : public ::Listener<linphone::Core, linphone::CoreListener>,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ Notification {
|
||||||
property string avatarUri: notificationData?.avatarUri
|
property string avatarUri: notificationData?.avatarUri
|
||||||
property string chatRoomName: notificationData?.chatRoomName ? notificationData.chatRoomName : ""
|
property string chatRoomName: notificationData?.chatRoomName ? notificationData.chatRoomName : ""
|
||||||
property string remoteAddress: notificationData?.remoteAddress ? notificationData.remoteAddress : ""
|
property string remoteAddress: notificationData?.remoteAddress ? notificationData.remoteAddress : ""
|
||||||
property string chatRoomAddress: notificationData?.chatRoomAddress ? notificationData.chatRoomAddress : ""
|
|
||||||
property bool isGroupChat: notificationData?.isGroupChat ? notificationData.isGroupChat : false
|
property bool isGroupChat: notificationData?.isGroupChat ? notificationData.isGroupChat : false
|
||||||
property string message: notificationData?.message ? notificationData.message : ""
|
property string message: notificationData?.message ? notificationData.message : ""
|
||||||
Connections {
|
Connections {
|
||||||
|
|
|
||||||
|
|
@ -1373,7 +1373,8 @@ AbstractWindow {
|
||||||
connectedCallButtons.visible = bottomButtonsLayout.visible
|
connectedCallButtons.visible = bottomButtonsLayout.visible
|
||||||
moreOptionsButtonVisibility = bottomButtonsLayout.visible
|
moreOptionsButtonVisibility = bottomButtonsLayout.visible
|
||||||
bottomButtonsLayout.layoutDirection = Qt.RightToLeft
|
bottomButtonsLayout.layoutDirection = Qt.RightToLeft
|
||||||
} else if (mainWindow.callState === LinphoneEnums.CallState.OutgoingInit) {
|
} else if (mainWindow.callState === LinphoneEnums.CallState.OutgoingInit
|
||||||
|
|| mainWindow.callState === LinphoneEnums.CallState.IncomingReceived) {
|
||||||
connectedCallButtons.visible = false
|
connectedCallButtons.visible = false
|
||||||
bottomButtonsLayout.layoutDirection = Qt.LeftToRight
|
bottomButtonsLayout.layoutDirection = Qt.LeftToRight
|
||||||
moreOptionsButtonVisibility = false
|
moreOptionsButtonVisibility = false
|
||||||
|
|
@ -1396,28 +1397,52 @@ AbstractWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// End call button
|
// End call button
|
||||||
BigButton {
|
RowLayout {
|
||||||
id: endCallButton
|
spacing: Utils.getSizeWithScreenRatio(10)
|
||||||
focus: true
|
BigButton {
|
||||||
Layout.row: 0
|
id: acceptCallButton
|
||||||
icon.width: Utils.getSizeWithScreenRatio(32)
|
visible: mainWindow.callState === LinphoneEnums.CallState.IncomingReceived
|
||||||
icon.height: Utils.getSizeWithScreenRatio(32)
|
Layout.row: 0
|
||||||
//: "Terminer l'appel"
|
icon.width: Utils.getSizeWithScreenRatio(32)
|
||||||
ToolTip.text: qsTr("call_action_end_call")
|
icon.height: Utils.getSizeWithScreenRatio(32)
|
||||||
Accessible.name: qsTr("call_action_end_call")
|
//: "Accepter l'appel"
|
||||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(75)
|
ToolTip.text: qsTr("call_action_accept_call")
|
||||||
Layout.preferredHeight: Utils.getSizeWithScreenRatio(55)
|
Accessible.name: qsTr("call_action_accept_call")
|
||||||
radius: Utils.getSizeWithScreenRatio(71)
|
Layout.preferredWidth: Utils.getSizeWithScreenRatio(75)
|
||||||
style: ButtonStyle.phoneRedLightBorder
|
Layout.preferredHeight: Utils.getSizeWithScreenRatio(55)
|
||||||
Layout.column: mainWindow.startingCall ? 0 : bottomButtonsLayout.columns - 1
|
radius: Utils.getSizeWithScreenRatio(71)
|
||||||
KeyNavigation.tab: mainWindow.startingCall ? (videoCameraButton.visible && videoCameraButton.enabled ? videoCameraButton : audioMicrophoneButton) : openStatisticPanelButton
|
style: ButtonStyle.phoneGreenLightBorder
|
||||||
KeyNavigation.backtab: mainWindow.startingCall ? rightPanel.visible ? Utils.getLastFocusableItemInItem(rightPanel) : nextItemInFocusChain(false): callListButton
|
Layout.column: mainWindow.startingCall ? 0 : bottomButtonsLayout.columns - 1
|
||||||
onClicked: {
|
KeyNavigation.tab: mainWindow.startingCall ? (videoCameraButton.visible && videoCameraButton.enabled ? videoCameraButton : audioMicrophoneButton) : openStatisticPanelButton
|
||||||
mainWindow.callTerminatedByUser = true
|
KeyNavigation.backtab:endCallButton
|
||||||
mainWindow.endCall(mainWindow.call)
|
onClicked: {
|
||||||
|
mainWindow.call.core.lAccept(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigButton {
|
||||||
|
id: endCallButton
|
||||||
|
focus: true
|
||||||
|
Layout.row: 0
|
||||||
|
icon.width: Utils.getSizeWithScreenRatio(32)
|
||||||
|
icon.height: Utils.getSizeWithScreenRatio(32)
|
||||||
|
//: "Terminer l'appel"
|
||||||
|
ToolTip.text: qsTr("call_action_end_call")
|
||||||
|
Accessible.name: qsTr("call_action_end_call")
|
||||||
|
Layout.preferredWidth: Utils.getSizeWithScreenRatio(75)
|
||||||
|
Layout.preferredHeight: Utils.getSizeWithScreenRatio(55)
|
||||||
|
radius: Utils.getSizeWithScreenRatio(71)
|
||||||
|
style: ButtonStyle.phoneRedLightBorder
|
||||||
|
Layout.column: mainWindow.startingCall ? 0 : bottomButtonsLayout.columns - 1
|
||||||
|
KeyNavigation.tab: mainWindow.startingCall ? (acceptCallButton.visible ? acceptCallButton : videoCameraButton.visible && videoCameraButton.enabled ? videoCameraButton : audioMicrophoneButton) : openStatisticPanelButton
|
||||||
|
KeyNavigation.backtab: mainWindow.startingCall ? rightPanel.visible ? Utils.getLastFocusableItemInItem(rightPanel) : nextItemInFocusChain(false): callListButton
|
||||||
|
onClicked: {
|
||||||
|
mainWindow.callTerminatedByUser = true
|
||||||
|
mainWindow.endCall(mainWindow.call)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Group button: pauseCall, transfertCall, newCall, callList
|
// Group button: pauseCall, transfertCall, newCall, callList
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,11 @@
|
||||||
pressed: Linphone.DefaultStyle.grey_0
|
pressed: Linphone.DefaultStyle.grey_0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var phoneGreenLightBorder = Object.assign({
|
||||||
|
borderColor : {
|
||||||
|
keybaordFocused: Linphone.DefaultStyle.main2_0
|
||||||
|
}
|
||||||
|
}, phoneGreen)
|
||||||
|
|
||||||
// Checkable
|
// Checkable
|
||||||
var checkable = {
|
var checkable = {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue