1,035 changes: 1,035 additions & 0 deletions external/wintoast/src/wintoastlib.cpp

Large diffs are not rendered by default.

163 changes: 163 additions & 0 deletions external/wintoast/src/wintoastlib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#ifndef WINTOASTLIB_H
#define WINTOASTLIB_H
#include <Windows.h>
#include <sdkddkver.h>
#include <WinUser.h>
#include <ShObjIdl.h>
#include <wrl/implements.h>
#include <wrl/event.h>
#include <windows.ui.notifications.h>
#include <strsafe.h>
#include <Psapi.h>
#include <ShlObj.h>
#include <roapi.h>
#include <propvarutil.h>
#include <functiondiscoverykeys.h>
#include <iostream>
#include <winstring.h>
#include <string.h>
#include <vector>
#include <map>
using namespace Microsoft::WRL;
using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::UI::Notifications;
using namespace Windows::Foundation;

#define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
#define DEFAULT_LINK_FORMAT L".lnk"
namespace WinToastLib {

class IWinToastHandler {
public:
enum WinToastDismissalReason {
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
};
virtual void toastActivated() const = 0;
virtual void toastActivated(int actionIndex) const = 0;
virtual void toastDismissed(WinToastDismissalReason state) const = 0;
virtual void toastFailed() const = 0;
};

class WinToastTemplate {
public:
enum Duration { System, Short, Long };
enum AudioOption { Default = 0, Silent = 1, Loop = 2 };
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
enum WinToastTemplateType {
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
WinToastTemplateTypeCount
};

WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
~WinToastTemplate();

void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
void setImagePath(_In_ const std::wstring& imgPath);
void setAudioPath(_In_ const std::wstring& audioPath);
void setAttributionText(_In_ const std::wstring & attributionText);
void addAction(_In_ const std::wstring& label);
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
void setDuration(_In_ Duration duration);
void setExpiration(_In_ INT64 millisecondsFromNow);
std::size_t textFieldsCount() const;
std::size_t actionsCount() const;
bool hasImage() const;
const std::vector<std::wstring>& textFields() const;
const std::wstring& textField(_In_ TextField pos) const;
const std::wstring& actionLabel(_In_ int pos) const;
const std::wstring& imagePath() const;
const std::wstring& audioPath() const;
const std::wstring& attributionText() const;
INT64 expiration() const;
WinToastTemplateType type() const;
WinToastTemplate::AudioOption audioOption() const;
Duration duration() const;
private:
std::vector<std::wstring> _textFields;
std::vector<std::wstring> _actions;
std::wstring _imagePath = L"";
std::wstring _audioPath = L"";
std::wstring _attributionText = L"";
INT64 _expiration = 0;
AudioOption _audioOption = WinToastTemplate::AudioOption::Default;
WinToastTemplateType _type = WinToastTemplateType::Text01;
Duration _duration = Duration::System;
};

class WinToast {
public:
enum WinToastError {
NoError = 0,
NotInitialized,
SystemNotSupported,
ShellLinkNotCreated,
InvalidAppUserModelID,
InvalidParameters,
InvalidHandler,
NotDisplayed,
UnknownError
};

enum ShortcutResult {
SHORTCUT_UNCHANGED = 0,
SHORTCUT_WAS_CHANGED = 1,
SHORTCUT_WAS_CREATED = 2,

SHORTCUT_MISSING_PARAMETERS = -1,
SHORTCUT_INCOMPATIBLE_OS = -2,
SHORTCUT_COM_INIT_FAILURE = -3,
SHORTCUT_CREATE_FAILED = -4
};

WinToast(void);
virtual ~WinToast();
static WinToast* instance();
static bool isCompatible();
static bool isSupportingModernFeatures();
static std::wstring configureAUMI(_In_ const std::wstring& companyName,
_In_ const std::wstring& productName,
_In_ const std::wstring& subProduct = std::wstring(),
_In_ const std::wstring& versionInformation = std::wstring()
);
virtual bool initialize(_Out_ WinToastError* error = nullptr);
virtual bool isInitialized() const;
virtual bool hideToast(_In_ INT64 id);
virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr);
virtual void clear();
virtual enum ShortcutResult createShortcut();

const std::wstring& appName() const;
const std::wstring& appUserModelId() const;
void setAppUserModelId(_In_ const std::wstring& appName);
void setAppName(_In_ const std::wstring& appName);

protected:
bool _isInitialized;
bool _hasCoInitialized;
std::wstring _appName;
std::wstring _aumi;
std::map<INT64, ComPtr<IToastNotification>> _buffer;

HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
HRESULT createShellLinkHelper();
HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path);
HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ int pos);
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
void setError(_Out_ WinToastError* error, _In_ WinToastError value);
};
}
#endif // WINTOASTLIB_H
2 changes: 1 addition & 1 deletion scripts/astyle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ astyleit() {

for f in "$@"; do
case "$f" in
src/plugins/grass/qtermwidget/*|external/o2/*|external/astyle/*|external/kdbush/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*)
src/plugins/grass/qtermwidget/*|external/o2/*|external/astyle/*|external/kdbush/*|external/wintoast/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*)
echo -ne "$f skipped $elcr"
continue
;;
Expand Down
7 changes: 6 additions & 1 deletion src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,11 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh

connect( QgsApplication::taskManager(), &QgsTaskManager::statusChanged, this, &QgisApp::onTaskCompleteShowNotify );

QgsGui::instance()->nativePlatformInterface()->initializeMainWindow( windowHandle() );
QgsGui::instance()->nativePlatformInterface()->initializeMainWindow( windowHandle(),
QgsApplication::applicationName(),
QgsApplication::organizationName(),
Qgis::QGIS_VERSION
);

// setup application progress reports from task manager
connect( QgsApplication::taskManager(), &QgsTaskManager::taskAdded, this, []
Expand Down Expand Up @@ -13849,6 +13853,7 @@ void QgisApp::showSystemNotification( const QString &title, const QString &messa
if ( replaceExisting )
settings.messageId = sLastMessageId;
settings.svgAppIconPath = QgsApplication::iconsPath() + QStringLiteral( "qgis_icon.svg" );
settings.pngAppIconPath = QgsApplication::appIconPath();

QgsNative::NotificationResult result = QgsGui::instance()->nativePlatformInterface()->showDesktopNotification( title, message, settings );

Expand Down
7 changes: 7 additions & 0 deletions src/native/CMakeLists.txt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ENDIF(APPLE)

IF(WIN32)
SET(QGIS_APP_WIN32_SRCS
../../external/wintoast/src/wintoastlib.cpp
win/qgswinnative.cpp
)
SET(QGIS_NATIVE_SRCS ${QGIS_NATIVE_SRCS}
Expand Down Expand Up @@ -93,6 +94,12 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_BINARY_DIR}
)

IF(WIN32)
INCLUDE_DIRECTORIES(SYSTEM
../../external/wintoast/src
)
ENDIF(WIN32)

#############################################################
# qgis_native library

Expand Down
5 changes: 4 additions & 1 deletion src/native/qgsnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ QgsNative::Capabilities QgsNative::capabilities() const
return nullptr;
}

void QgsNative::initializeMainWindow( QWindow * )
void QgsNative::initializeMainWindow( QWindow *,
const QString &,
const QString &,
const QString & )
{

}
Expand Down
11 changes: 10 additions & 1 deletion src/native/qgsnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ class NATIVE_EXPORT QgsNative
/**
* Initializes the native interface, using the specified \a window.
*
* The \a applicationName, \a organizationName and \a version information
* are used to initialize application-wide settings, depending on the platform.
*
* The default implementation does nothing.
*
* \since QGIS 3.4
*/
virtual void initializeMainWindow( QWindow *window );
virtual void initializeMainWindow( QWindow *window,
const QString &applicationName,
const QString &organizationName,
const QString &version );

/**
* Brings the QGIS app to front. The default implementation does nothing.
Expand Down Expand Up @@ -129,6 +135,9 @@ class NATIVE_EXPORT QgsNative
//! Path to application icon in SVG format
QString svgAppIconPath;

//! Path to application icon in png format
QString pngAppIconPath;

//! Message ID, used to replace existing messages
QVariant messageId;
};
Expand Down
57 changes: 56 additions & 1 deletion src/native/win/qgswinnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "qgswinnative.h"
#include <QCoreApplication>
#include <QDebug>
#include <QString>
#include <QDir>
#include <QWindow>
Expand All @@ -25,8 +26,17 @@
#include <QtWinExtras/QWinJumpList>
#include <QtWinExtras/QWinJumpListItem>
#include <QtWinExtras/QWinJumpListCategory>
#include "wintoastlib.h"

void QgsWinNative::initializeMainWindow( QWindow *window )
QgsNative::Capabilities QgsWinNative::capabilities() const
{
return mCapabilities;
}

void QgsWinNative::initializeMainWindow( QWindow *window,
const QString &applicationName,
const QString &organizationName,
const QString &version )
{
if ( mTaskButton )
return; // already initialized!
Expand All @@ -35,6 +45,17 @@ void QgsWinNative::initializeMainWindow( QWindow *window )
mTaskButton->setWindow( window );
mTaskProgress = mTaskButton->progress();
mTaskProgress->setVisible( false );

WinToastLib::WinToast::instance()->setAppName( applicationName.toStdWString() );
WinToastLib::WinToast::instance()->setAppUserModelId(
WinToastLib::WinToast::configureAUMI( organizationName.toStdWString(),
applicationName.toStdWString(),
applicationName.toStdWString(),
version.toStdWString() ) );
if ( WinToastLib::WinToast::instance()->initialize() )
{
mCapabilities = mCapabilities | NativeDesktopNotifications;
}
}

void QgsWinNative::openFileExplorerAndSelectFile( const QString &path )
Expand Down Expand Up @@ -80,3 +101,37 @@ void QgsWinNative::onRecentProjectsChanged( const std::vector<QgsNative::RecentP
jumplist.recent()->addItem( newProject );
}
}

class NotificationHandler : public WinToastLib::IWinToastHandler
{
public:

void toastActivated() const override {}

void toastActivated( int ) const override {}

void toastFailed() const
{
qWarning() << "Error showing notification";
}

void toastDismissed( WinToastDismissalReason ) const override {}
};


QgsNative::NotificationResult QgsWinNative::showDesktopNotification( const QString &summary, const QString &body, const QgsNative::NotificationSettings &settings )
{
WinToastLib::WinToastTemplate templ = WinToastLib::WinToastTemplate( WinToastLib::WinToastTemplate::ImageAndText02 );
templ.setImagePath( settings.pngAppIconPath.toStdWString() );
templ.setTextField( summary.toStdWString(), WinToastLib::WinToastTemplate::FirstLine );
templ.setTextField( body.toStdWString(), WinToastLib::WinToastTemplate::SecondLine );
templ.setDuration( WinToastLib::WinToastTemplate::Short );

NotificationResult result;
if ( WinToastLib::WinToast::instance()->showToast( templ, new NotificationHandler ) < 0 )
result.successful = false;
else
result.successful = true;

return result;
}
8 changes: 7 additions & 1 deletion src/native/win/qgswinnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@ class QWindow;
class NATIVE_EXPORT QgsWinNative : public QgsNative
{
public:
void initializeMainWindow( QWindow *window ) override;
Capabilities capabilities() const override;
void initializeMainWindow( QWindow *window,
const QString &applicationName,
const QString &organizationName,
const QString &version ) override;
void openFileExplorerAndSelectFile( const QString &path ) override;
void showUndefinedApplicationProgress() override;
void setApplicationProgress( double progress ) override;
void hideApplicationProgress() override;
void onRecentProjectsChanged( const std::vector< RecentProjectProperties > &recentProjects ) override;
NotificationResult showDesktopNotification( const QString &summary, const QString &body, const NotificationSettings &settings = NotificationSettings() ) override;

private:

Capabilities mCapabilities = nullptr;
QWinTaskbarButton *mTaskButton = nullptr;
QWinTaskbarProgress *mTaskProgress = nullptr;
};
Expand Down