mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-04-17 11:58:27 +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})
|
||||
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")
|
||||
|
||||
|
|
@ -239,4 +242,4 @@ if (WIN32)
|
|||
install(FILES "$<TARGET_PDB_FILE:${T}>" DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endforeach ()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@
|
|||
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
|
||||
#endif // if defined(Q_OS_MACOS)
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include "core/notifier/WindowsNotificationBackend.hpp"
|
||||
#endif
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(App)
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
|
@ -614,6 +618,10 @@ int App::getEventCount() const {
|
|||
return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0;
|
||||
}
|
||||
|
||||
NotificationBackend *App::getNotificationBackend() const {
|
||||
return mNotificationBackend;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Initializations
|
||||
//-----------------------------------------------------------
|
||||
|
|
@ -744,6 +752,8 @@ void App::initCore() {
|
|||
mEngine->setObjectOwnership(settings.get(), QQmlEngine::CppOwnership);
|
||||
mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
|
||||
mNotificationBackend = new NotificationBackend(this);
|
||||
|
||||
auto initLists = [this] {
|
||||
if (mCoreStarted) {
|
||||
if (!mAccountList) setAccountList(AccountList::create());
|
||||
|
|
@ -1086,7 +1096,6 @@ void App::clean() {
|
|||
mDateUpdateTimer.stop();
|
||||
#ifdef Q_OS_WIN
|
||||
removeNativeEventFilter(mLockEventFilter);
|
||||
delete mLockEventFilter;
|
||||
#endif
|
||||
if (mEngine) {
|
||||
mEngine->clearComponentCache();
|
||||
|
|
@ -1698,8 +1707,9 @@ void App::setSysTrayIcon() {
|
|||
|
||||
//
|
||||
#ifdef Q_OS_WIN
|
||||
if (!mLockEventFilter) mLockEventFilter = new LockEventFilter();
|
||||
connect(mLockEventFilter, &LockEventFilter::sessionUnlocked, this, [this] { emit sessionUnlocked(); });
|
||||
if (!mLockEventFilter) mLockEventFilter = new LockEventFilter(this);
|
||||
connect(mLockEventFilter, &LockEventFilter::sessionLockedChanged, this,
|
||||
[this](bool locked) { setSessionLocked(locked); });
|
||||
installNativeEventFilter(mLockEventFilter);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1807,6 +1817,20 @@ void App::setScreenRatio(float 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 *markAllReadAction = new QAction(tr("mark_all_read_action"), window);
|
||||
window->connect(markAllReadAction, &QAction::triggered, this, [this] {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class Notifier;
|
|||
class QQuickWindow;
|
||||
class QSystemTrayIcon;
|
||||
class DefaultTranslatorCore;
|
||||
class NotificationBackend;
|
||||
|
||||
class App : public SingleApplication, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
|
@ -197,11 +198,18 @@ public:
|
|||
QString getSdkVersion();
|
||||
QString getQtVersion() const;
|
||||
|
||||
NotificationBackend *getNotificationBackend() const;
|
||||
|
||||
Q_INVOKABLE void checkForUpdate(bool requestedByUser = false);
|
||||
|
||||
float getScreenRatio() const;
|
||||
Q_INVOKABLE void setScreenRatio(float ratio);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void setSessionLocked(bool locked);
|
||||
bool getSessionLocked() const;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
Q_INVOKABLE void exportDesktopFile();
|
||||
|
||||
|
|
@ -233,7 +241,7 @@ signals:
|
|||
void remainingTimeBeforeOidcTimeoutChanged();
|
||||
void currentAccountChanged();
|
||||
#ifdef Q_OS_WIN
|
||||
void sessionUnlocked();
|
||||
void sessionLockedChanged();
|
||||
#endif
|
||||
// void executeCommand(QString command);
|
||||
|
||||
|
|
@ -248,6 +256,7 @@ private:
|
|||
Thread *mLinphoneThread = nullptr;
|
||||
Notifier *mNotifier = nullptr;
|
||||
EventCountNotifier *mEventCountNotifier = nullptr;
|
||||
NotificationBackend *mNotificationBackend = nullptr;
|
||||
QSystemTrayIcon *mSystemTrayIcon = nullptr;
|
||||
QQuickWindow *mMainWindow = nullptr;
|
||||
QQuickWindow *mCallsWindow = nullptr;
|
||||
|
|
@ -278,6 +287,8 @@ private:
|
|||
float mScreenRatio = 1;
|
||||
QTimer mOIDCRefreshTimer;
|
||||
int mRemainingTimeBeforeOidcTimeout = 0;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool mSessionLocked = false;
|
||||
#endif
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
core/logger/QtLogger.cpp
|
||||
core/login/LoginPage.cpp
|
||||
core/notifier/Notifier.cpp
|
||||
core/notifier/AbstractNotificationBackend.cpp
|
||||
core/path/Paths.cpp
|
||||
core/phone-number/PhoneNumber.cpp
|
||||
core/phone-number/PhoneNumberList.cpp
|
||||
|
|
@ -129,6 +130,12 @@ else() # Use QDBus for Linux
|
|||
core/singleapplication/SingleApplicationDBusPrivate.hpp
|
||||
core/singleapplication/SingleApplicationDBus.cpp)
|
||||
endif()
|
||||
if(WIN32)
|
||||
list(APPEND _LINPHONEAPP_SOURCES
|
||||
core/notifier/NotificationActivator.cpp
|
||||
core/notifier/DesktopNotificationManagerCompat.cpp
|
||||
core/notifier/WindowsNotificationBackend.cpp)
|
||||
endif()
|
||||
if(APPLE)
|
||||
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierMacOs.m)
|
||||
else()
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
|
|||
auto remoteAddress = call->getCallLog()->getRemoteAddress();
|
||||
mRemoteAddress = Utils::coreStringToAppString(remoteAddress->asStringUriOnly());
|
||||
mRemoteUsername = Utils::coreStringToAppString(remoteAddress->getUsername());
|
||||
mCallId = Utils::coreStringToAppString(call->getCallLog()->getCallId());
|
||||
auto linphoneFriend = ToolModel::findFriendByAddress(remoteAddress);
|
||||
if (linphoneFriend)
|
||||
mRemoteName = Utils::coreStringToAppString(
|
||||
|
|
@ -520,6 +521,10 @@ QString CallCore::getLocalAddress() const {
|
|||
return mLocalAddress;
|
||||
}
|
||||
|
||||
QString CallCore::getCallId() const {
|
||||
return mCallId;
|
||||
}
|
||||
|
||||
LinphoneEnums::CallStatus CallCore::getStatus() const {
|
||||
return mStatus;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ public:
|
|||
QString getRemoteAddress() const;
|
||||
QString getLocalAddress() const;
|
||||
|
||||
QString getCallId() const;
|
||||
|
||||
LinphoneEnums::CallStatus getStatus() const;
|
||||
void setStatus(LinphoneEnums::CallStatus status);
|
||||
|
||||
|
|
@ -334,6 +336,7 @@ private:
|
|||
QString mRemoteUsername;
|
||||
QString mRemoteAddress;
|
||||
QString mLocalAddress;
|
||||
QString mCallId;
|
||||
bool mTokenVerified = false;
|
||||
bool mIsSecured = false;
|
||||
bool mIsMismatch = false;
|
||||
|
|
|
|||
|
|
@ -27,12 +27,8 @@ bool LockEventFilter::nativeEventFilter(const QByteArray &eventType, void *messa
|
|||
#ifdef Q_OS_WIN
|
||||
MSG *msg = static_cast<MSG *>(message);
|
||||
if (msg->message == WM_WTSSESSION_CHANGE) {
|
||||
if (msg->wParam == WTS_SESSION_LOCK) {
|
||||
return true;
|
||||
} else {
|
||||
emit sessionUnlocked();
|
||||
return true;
|
||||
}
|
||||
emit sessionLockedChanged(msg->wParam == WTS_SESSION_LOCK);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public:
|
|||
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||
|
||||
signals:
|
||||
void sessionUnlocked();
|
||||
void sessionLockedChanged(bool locked);
|
||||
};
|
||||
|
||||
#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 "Notifier.hpp"
|
||||
#include "WindowsNotificationBackend.hpp"
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "core/call/CallGui.hpp"
|
||||
#include "core/chat/ChatGui.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
#include "tool/accessibility/AccessibilityHelper.hpp"
|
||||
#include "tool/providers/AvatarProvider.hpp"
|
||||
#include "tool/providers/ImageProvider.hpp"
|
||||
|
||||
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 = {
|
||||
{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
|
||||
//{Notifier::ReceivedFileMessage, {Notifier::ReceivedFileMessage, "NotificationReceivedFileMessage.qml", 10}},
|
||||
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}}
|
||||
{AbstractNotificationBackend::ReceivedMessage,
|
||||
{AbstractNotificationBackend::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
|
||||
//{AbstractNotificationBackend::ReceivedFileMessage, {AbstractNotificationBackend::ReceivedFileMessage,
|
||||
//"NotificationReceivedFileMessage.qml", 10}},
|
||||
{AbstractNotificationBackend::ReceivedCall,
|
||||
{AbstractNotificationBackend::ReceivedCall, "NotificationReceivedCall.qml", 30}}
|
||||
//{Notifier::NewVersionAvailable, {Notifier::NewVersionAvailable, "NotificationNewVersionAvailable.qml", 30}},
|
||||
//{Notifier::SnapshotWasTaken, {Notifier::SnapshotWasTaken, "NotificationSnapshotWasTaken.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) {
|
||||
mustBeInMainThread(getClassName());
|
||||
mustBeInMainThread("Notifier");
|
||||
const int nComponents = Notifications.size();
|
||||
mComponents.resize(nComponents);
|
||||
|
||||
|
|
@ -118,113 +119,123 @@ Notifier::Notifier(QObject *parent) : QObject(parent) {
|
|||
}
|
||||
|
||||
Notifier::~Notifier() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
mustBeInMainThread("~Notifier");
|
||||
delete mMutex;
|
||||
|
||||
const int nComponents = Notifications.size();
|
||||
mComponents.clear();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap data) {
|
||||
bool Notifier::createNotification(AbstractNotificationBackend::NotificationType type, QVariantMap data) {
|
||||
mMutex->lock();
|
||||
// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
|
||||
if (mInstancesNumber >= MaxNotificationsNumber) { // Check existing instances.
|
||||
qWarning() << QStringLiteral("Unable to create another notification.");
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
auto notifBackend = App::getInstance()->getNotificationBackend();
|
||||
if (!notifBackend) {
|
||||
qWarning() << QStringLiteral("Unable to get notification backend, return.");
|
||||
mMutex->unlock();
|
||||
return false;
|
||||
}
|
||||
QList<QScreen *> allScreens = QGuiApplication::screens();
|
||||
if (allScreens.size() > 0) { // Ensure to have a screen to avoid errors
|
||||
QQuickItem *previousWrapper = nullptr;
|
||||
bool showAsTool = false;
|
||||
notifBackend->sendNotification(type, data);
|
||||
#else
|
||||
|
||||
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
|
||||
for (auto w : QGuiApplication::topLevelWindows()) {
|
||||
if (w->visibility() == QWindow::FullScreen) {
|
||||
showAsTool = true;
|
||||
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)
|
||||
for (auto w : QGuiApplication::topLevelWindows()) {
|
||||
if (w->visibility() == QWindow::FullScreen) {
|
||||
showAsTool = true;
|
||||
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)
|
||||
}
|
||||
}
|
||||
#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();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -237,17 +248,6 @@ void Notifier::showNotification(QQuickWindow *notification, int timeout) {
|
|||
timer->setInterval(timeout);
|
||||
timer->setSingleShot(true);
|
||||
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->showNormal();
|
||||
// notification->raise();
|
||||
|
|
@ -322,8 +322,8 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
|
|||
if (account) {
|
||||
auto accountModel = Utils::makeQObject_ptr<AccountModel>(account);
|
||||
if (!accountModel->getNotificationsAllowed()) {
|
||||
lInfo() << log().arg(
|
||||
"Notifications have been disabled for this account - not creating a notification for incoming call");
|
||||
lInfo() << log().arg("Notifications have been disabled for this account - not creating a notification "
|
||||
"for incoming call");
|
||||
if (accountModel->forwardToVoiceMailInDndPresence()) {
|
||||
lInfo() << log().arg("Transferring call to voicemail");
|
||||
auto voicemailAddress = linphone::Factory::get()->createAddress(
|
||||
|
|
@ -353,7 +353,8 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
|
|||
|
||||
map["displayName"].setValue(displayName);
|
||||
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);
|
||||
auto fileContent = message->getFileTransferInformation();
|
||||
if (!fileContent) {
|
||||
for (auto content : message->getContents()) {
|
||||
for (const auto &content : message->getContents()) {
|
||||
if (content->isText()) txt += content->getUtf8Text().c_str();
|
||||
}
|
||||
} else if (fileContent->isVoiceRecording())
|
||||
|
|
@ -466,7 +467,7 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
|||
map["avatarUri"] = chatCore->getAvatarUri();
|
||||
map["isGroupChat"] = chatCore->isGroupChat();
|
||||
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 "core/notifier/AbstractNotificationBackend.hpp"
|
||||
|
||||
class QMutex;
|
||||
class QQmlComponent;
|
||||
|
||||
|
|
@ -40,15 +42,6 @@ public:
|
|||
Notifier(QObject *parent = Q_NULLPTR);
|
||||
~Notifier();
|
||||
|
||||
enum NotificationType {
|
||||
ReceivedMessage,
|
||||
// ReceivedFileMessage,
|
||||
ReceivedCall
|
||||
// NewVersionAvailable,
|
||||
// SnapshotWasTaken,
|
||||
// RecordingCompleted
|
||||
};
|
||||
|
||||
// void notifyReceivedCall(Call *call);
|
||||
void notifyReceivedCall(const std::shared_ptr<linphone::Call> &call); // Call from Linphone
|
||||
|
||||
|
|
@ -77,7 +70,7 @@ private:
|
|||
this->timeout = timeout;
|
||||
}
|
||||
int getTimeout() const {
|
||||
if (type == Notifier::ReceivedCall) {
|
||||
if (type == AbstractNotificationBackend::ReceivedCall) {
|
||||
// return CoreManager::getInstance()->getSettingsModel()->getIncomingCallTimeout();
|
||||
return 30;
|
||||
} else return timeout;
|
||||
|
|
@ -89,7 +82,7 @@ private:
|
|||
int type;
|
||||
};
|
||||
|
||||
bool createNotification(NotificationType type, QVariantMap data);
|
||||
bool createNotification(AbstractNotificationBackend::NotificationType type, QVariantMap data);
|
||||
void showNotification(QQuickWindow *notification, int timeout);
|
||||
|
||||
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 <QQmlApplicationEngine>
|
||||
#include <qloggingcategory.h>
|
||||
|
||||
#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/logger/QtLogger.hpp"
|
||||
#include "core/path/Paths.hpp"
|
||||
|
||||
#include "core/notifier/DesktopNotificationManagerCompat.hpp"
|
||||
#include "core/notifier/NotificationActivator.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLocale>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QTranslator>
|
||||
#include <iostream>
|
||||
#include <qloggingcategory.h>
|
||||
|
||||
#ifdef QT_QML_DEBUG
|
||||
#include <QQmlDebuggingEnabler>
|
||||
#include <QStandardPaths>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
FILE *gStream = NULL;
|
||||
#endif
|
||||
#define WIDEN2(x) L##x
|
||||
#define WIDEN(x) WIDEN2(x)
|
||||
|
||||
#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
|
||||
#include <QAccessible>
|
||||
#include <QAccessibleEvent>
|
||||
|
||||
void DummyUpdateHandler(QAccessibleEvent *event) {
|
||||
}
|
||||
void DummyRootObjectHandler(QObject *) {
|
||||
}
|
||||
#endif
|
||||
static const wchar_t *mAumid = WIDEN(APPLICATION_ID);
|
||||
|
||||
void cleanStream() {
|
||||
#ifdef _WIN32
|
||||
|
|
@ -53,6 +55,19 @@ int main(int argc, char *argv[]) {
|
|||
#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
|
||||
lDebug() << "[Main] Setting ShareOpenGLContexts";
|
||||
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||
|
|
@ -67,8 +82,25 @@ int main(int argc, char *argv[]) {
|
|||
lDebug() << "[Main] Setting application to UTF8";
|
||||
setlocale(LC_CTYPE, ".UTF8");
|
||||
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);
|
||||
|
||||
hr = DesktopNotificationManagerCompat::RegisterActivator();
|
||||
if (FAILED(hr)) {
|
||||
qWarning() << "RegisterActivator failed:" << Qt::hex << hr;
|
||||
}
|
||||
|
||||
#ifdef ACCESSBILITY_WORKAROUND
|
||||
QAccessible::installUpdateHandler(DummyUpdateHandler);
|
||||
QAccessible::installRootObjectHandler(DummyRootObjectHandler);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
|
||||
// =============================================================================
|
||||
|
||||
class AbstractEventCountNotifier;
|
||||
class EventCountNotifier;
|
||||
|
||||
class CoreModel : public ::Listener<linphone::Core, linphone::CoreListener>,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ Notification {
|
|||
property string avatarUri: notificationData?.avatarUri
|
||||
property string chatRoomName: notificationData?.chatRoomName ? notificationData.chatRoomName : ""
|
||||
property string remoteAddress: notificationData?.remoteAddress ? notificationData.remoteAddress : ""
|
||||
property string chatRoomAddress: notificationData?.chatRoomAddress ? notificationData.chatRoomAddress : ""
|
||||
property bool isGroupChat: notificationData?.isGroupChat ? notificationData.isGroupChat : false
|
||||
property string message: notificationData?.message ? notificationData.message : ""
|
||||
Connections {
|
||||
|
|
|
|||
|
|
@ -1373,7 +1373,8 @@ AbstractWindow {
|
|||
connectedCallButtons.visible = bottomButtonsLayout.visible
|
||||
moreOptionsButtonVisibility = bottomButtonsLayout.visible
|
||||
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
|
||||
bottomButtonsLayout.layoutDirection = Qt.LeftToRight
|
||||
moreOptionsButtonVisibility = false
|
||||
|
|
@ -1396,28 +1397,52 @@ AbstractWindow {
|
|||
}
|
||||
|
||||
// End call button
|
||||
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 ? (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)
|
||||
RowLayout {
|
||||
spacing: Utils.getSizeWithScreenRatio(10)
|
||||
BigButton {
|
||||
id: acceptCallButton
|
||||
visible: mainWindow.callState === LinphoneEnums.CallState.IncomingReceived
|
||||
Layout.row: 0
|
||||
icon.width: Utils.getSizeWithScreenRatio(32)
|
||||
icon.height: Utils.getSizeWithScreenRatio(32)
|
||||
//: "Accepter l'appel"
|
||||
ToolTip.text: qsTr("call_action_accept_call")
|
||||
Accessible.name: qsTr("call_action_accept_call")
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(75)
|
||||
Layout.preferredHeight: Utils.getSizeWithScreenRatio(55)
|
||||
radius: Utils.getSizeWithScreenRatio(71)
|
||||
style: ButtonStyle.phoneGreenLightBorder
|
||||
Layout.column: mainWindow.startingCall ? 0 : bottomButtonsLayout.columns - 1
|
||||
KeyNavigation.tab: mainWindow.startingCall ? (videoCameraButton.visible && videoCameraButton.enabled ? videoCameraButton : audioMicrophoneButton) : openStatisticPanelButton
|
||||
KeyNavigation.backtab:endCallButton
|
||||
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
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -137,6 +137,11 @@
|
|||
pressed: Linphone.DefaultStyle.grey_0
|
||||
}
|
||||
}
|
||||
var phoneGreenLightBorder = Object.assign({
|
||||
borderColor : {
|
||||
keybaordFocused: Linphone.DefaultStyle.main2_0
|
||||
}
|
||||
}, phoneGreen)
|
||||
|
||||
// Checkable
|
||||
var checkable = {
|
||||
|
|
@ -395,4 +400,4 @@
|
|||
hovered: Linphone.DefaultStyle.main2_100,
|
||||
pressed: Linphone.DefaultStyle.main2_100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue