Skip to content
Permalink
Browse files

Add API to send native desktop notifications to QgsNative

And use native desktop notifications on Linux (via dbus). Avoids
unnecessary system tray icon creation, which is a pre-requisite
for the Qt desktop notification implemention. This should avoid
annoying notifications on certain Linux desktop environments,
and should also allow us to use prettier Windows notifications
(when implemented!)
  • Loading branch information
nyalldawson committed Aug 6, 2018
1 parent 282f95c commit 64586df297c2d822f3e1dda58af53c2054161813
Showing with 249 additions and 11 deletions.
  1. +36 −10 src/app/qgisapp.cpp
  2. +2 −1 src/app/qgisapp.h
  3. +120 −0 src/native/linux/qgslinuxnative.cpp
  4. +12 −0 src/native/linux/qgslinuxnative.h
  5. +12 −0 src/native/qgsnative.cpp
  6. +67 −0 src/native/qgsnative.h
@@ -94,6 +94,9 @@
#include "processing/qgs3dalgorithms.h"
#endif

#include "qgsgui.h"
#include "qgsnative.h"

#include <QNetworkReply>
#include <QNetworkProxy>
#include <QAuthenticator>
@@ -707,9 +710,12 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
}
endProfile();

mTray = new QSystemTrayIcon();
mTray->setIcon( QIcon( QgsApplication::appIconPath() ) );
mTray->hide();
if ( !( QgsGui::nativePlatformInterface()->capabilities() & QgsNative::NativeDesktopNotifications ) )
{
mTray = new QSystemTrayIcon();
mTray->setIcon( QIcon( QgsApplication::appIconPath() ) );
mTray->hide();
}

// Create the themes folder for the user
startProfile( QStringLiteral( "Creating theme folder" ) );
@@ -1387,7 +1393,6 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
{
mCentralContainer->setCurrentIndex( 0 );
} );

} // QgisApp ctor

QgisApp::QgisApp()
@@ -13844,13 +13849,34 @@ QMenu *QgisApp::createPopupMenu()
}


void QgisApp::showSystemNotification( const QString &title, const QString &message )
void QgisApp::showSystemNotification( const QString &title, const QString &message, bool replaceExisting )
{
// Menubar icon is hidden by default. Show to enable notification bubbles
mTray->show();
mTray->showMessage( title, message );
// Re-hide menubar icon
mTray->hide();
static QVariant sLastMessageId;

QgsNative::NotificationSettings settings;
settings.transient = true;
if ( replaceExisting )
settings.messageId = sLastMessageId;
settings.svgAppIconPath = QgsApplication::iconsPath() + QStringLiteral( "qgis_icon.svg" );

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

if ( !result.successful )
{
// fallback - use system tray notification
if ( mTray )
{
// Menubar icon is hidden by default. Show to enable notification bubbles
mTray->show();
mTray->showMessage( title, message );
// Re-hide menubar icon
mTray->hide();
}
}
else
{
sLastMessageId = result.messageId;
}
}

void QgisApp::showStatisticsDockWidget( bool show )
@@ -416,8 +416,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*
* \param title
* \param message
* \param replaceExisting set to true to replace any existing notifications, or false to add a new notification
*/
void showSystemNotification( const QString &title, const QString &message );
void showSystemNotification( const QString &title, const QString &message, bool replaceExisting = true );


//! Actions to be inserted in menus and toolbars
@@ -21,6 +21,12 @@
#include <QString>
#include <QtDBus/QtDBus>
#include <QtDebug>
#include <QImage>

QgsNative::Capabilities QgsLinuxNative::capabilities() const
{
return NativeDesktopNotifications;
}

void QgsLinuxNative::openFileExplorerAndSelectFile( const QString &path )
{
@@ -41,3 +47,117 @@ void QgsLinuxNative::openFileExplorerAndSelectFile( const QString &path )
QgsNative::openFileExplorerAndSelectFile( path );
}
}

/**
* Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify
*
* This function is from the Clementine project (see
* http://www.clementine-player.org) and licensed under the GNU General Public
* License, version 3 or later.
*
* Copyright 2010, David Sansome <me@davidsansome.com>
*/
QDBusArgument &operator<<( QDBusArgument &arg, const QImage &image )
{
if ( image.isNull() )
{
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}

QImage scaled = image.scaledToHeight( 100, Qt::SmoothTransformation );
scaled = scaled.convertToFormat( QImage::Format_ARGB32 );

#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
#else
// ABGR -> GBAR
QImage i( scaled.size(), scaled.format() );
for ( int y = 0; y < i.height(); ++y )
{
QRgb *p = ( QRgb * ) scaled.scanLine( y );
QRgb *q = ( QRgb * ) i.scanLine( y );
QRgb *end = p + scaled.width();
while ( p < end )
{
*q = qRgba( qGreen( *p ), qBlue( *p ), qAlpha( *p ), qRed( *p ) );
p++;
q++;
}
}
#endif

arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : ( i.hasAlphaChannel() ? 4 : 3 );
arg << i.depth() / channels;
arg << channels;
arg << QByteArray( reinterpret_cast<const char *>( i.bits() ), i.numBytes() );
arg.endStructure();
return arg;
}

const QDBusArgument &operator>>( const QDBusArgument &arg, QImage & )
{
// This is needed to link but shouldn't be called.
Q_ASSERT( 0 );
return arg;
}

QgsNative::NotificationResult QgsLinuxNative::showDesktopNotification( const QString &summary, const QString &body, const NotificationSettings &settings )
{
NotificationResult result;
result.successful = false;

if ( !QDBusConnection::sessionBus().isConnected() )
{
return result;
}

qDBusRegisterMetaType<QImage>();

QDBusInterface iface( QStringLiteral( "org.freedesktop.Notifications" ),
QStringLiteral( "/org/freedesktop/Notifications" ),
QStringLiteral( "org.freedesktop.Notifications" ),
QDBusConnection::sessionBus() );

QVariantMap hints;
hints[QStringLiteral( "transient" )] = settings.transient;
if ( !settings.image.isNull() )
hints[QStringLiteral( "image_data" )] = settings.image;

QVariantList argumentList;
argumentList << "qgis"; //app_name
// replace_id
if ( settings.messageId.isValid() )
argumentList << static_cast< uint >( settings.messageId.toInt() );
else
argumentList << static_cast< uint >( 0 );
// app_icon
if ( !settings.svgAppIconPath.isEmpty() )
argumentList << settings.svgAppIconPath;
else
argumentList << "";
argumentList << summary; // summary
argumentList << body; // body
argumentList << QStringList(); // actions
argumentList << hints; // hints
argumentList << -1; // timeout in ms "If -1, the notification's expiration time is dependent on the notification server's settings, and may vary for the type of notification."

QDBusMessage reply = iface.callWithArgumentList( QDBus::AutoDetect, QStringLiteral( "Notify" ), argumentList );
if ( reply.type() == QDBusMessage::ErrorMessage )
{
qDebug() << "D-Bus Error:" << reply.errorMessage();
return result;
}

result.successful = true;
result.messageId = reply.arguments().value( 0 );
return result;
}
@@ -20,10 +20,22 @@

#include "qgsnative.h"

/**
* Native implementation for linux platforms.
*
* Implements the native platform interface for Linux based platforms. This is
* intended to expose desktop-agnostic implementations of the QgsNative methods,
* so that they work without issue across the wide range of Linux desktop environments
* (e.g. Gnome/KDE).
*
* Typically, this means implementing methods using DBUS calls to freedesktop standards.
*/
class NATIVE_EXPORT QgsLinuxNative : public QgsNative
{
public:
QgsNative::Capabilities capabilities() const override;
void openFileExplorerAndSelectFile( const QString &path ) override;
NotificationResult showDesktopNotification( const QString &summary, const QString &body, const NotificationSettings &settings = NotificationSettings() ) override;
};

#endif // QGSLINUXNATIVE_H
@@ -21,6 +21,11 @@
#include <QUrl>
#include <QFileInfo>

QgsNative::Capabilities QgsNative::capabilities() const
{
return nullptr;
}

void QgsNative::initializeMainWindow( QWindow * )
{

@@ -51,3 +56,10 @@ void QgsNative::hideApplicationProgress()
{

}

QgsNative::NotificationResult QgsNative::showDesktopNotification( const QString &, const QString &, const NotificationSettings & )
{
NotificationResult result;
result.successful = false;
return result;
}
@@ -19,6 +19,8 @@
#define QGSNATIVE_H

#include "qgis_native.h"
#include <QImage>
#include <QVariant>

class QString;
class QWindow;
@@ -34,8 +36,20 @@ class NATIVE_EXPORT QgsNative
{
public:

//! Native interface capabilities
enum Capability
{
NativeDesktopNotifications = 1 << 1, //!< Native desktop notifications are supported. See showDesktopNotification().
};
Q_DECLARE_FLAGS( Capabilities, Capability )

virtual ~QgsNative() = default;

/**
* Returns the native interface's capabilities.
*/
virtual Capabilities capabilities() const;

/**
* Initializes the native interface, using the specified \a window.
*
@@ -97,6 +111,59 @@ class NATIVE_EXPORT QgsNative
*/
virtual void hideApplicationProgress();


/**
* Notification settings, for use with showDesktopNotification().
*/
struct NotificationSettings
{
NotificationSettings() {}

//! Optional image to show in notification
QImage image;

//! Whether the notification should be transient (the meaning varies depending on platform)
bool transient = true;

//! Path to application icon in SVG format
QString svgAppIconPath;

//! Message ID, used to replace existing messages
QVariant messageId;
};

/**
* Result of sending a desktop notification, returned by showDesktopNotification().
*/
struct NotificationResult
{
NotificationResult() {}

//! True if notification was successfully sent.
bool successful = false;

//! Unique notification message ID, used by some platforms to identify the notification
QVariant messageId;
};

/**
* Shows a native desktop notification.
*
* The \a summary argument specifies a short title for the notification, and the \a body argument
* specifies the complete notification message text.
*
* The \a settings argument allows fine-tuning of the notification, using a range of settings which
* may or may not be respected on all platforms. It is recommended to set as many of these properties
* as is applicable, and let the native implementation choose which to use based on the platform's
* capabilities.
*
* This method is only supported when the interface returns the NativeDesktopNotifications flag for capabilities().
*
* Returns true if notification was successfully sent.
*/
virtual NotificationResult showDesktopNotification( const QString &summary, const QString &body, const NotificationSettings &settings = NotificationSettings() );
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsNative::Capabilities )

#endif // QGSNATIVE_H

0 comments on commit 64586df

Please sign in to comment.
You can’t perform that action at this time.