Skip to content

Commit

Permalink
Add API to send native desktop notifications to QgsNative
Browse files Browse the repository at this point in the history
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 64586df
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 11 deletions.
46 changes: 36 additions & 10 deletions src/app/qgisapp.cpp
Expand Up @@ -94,6 +94,9 @@
#include "processing/qgs3dalgorithms.h"
#endif

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

#include <QNetworkReply>
#include <QNetworkProxy>
#include <QAuthenticator>
Expand Down Expand Up @@ -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" ) );
Expand Down Expand Up @@ -1387,7 +1393,6 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
{
mCentralContainer->setCurrentIndex( 0 );
} );

} // QgisApp ctor

QgisApp::QgisApp()
Expand Down Expand Up @@ -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 )
Expand Down
3 changes: 2 additions & 1 deletion src/app/qgisapp.h
Expand Up @@ -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
Expand Down
120 changes: 120 additions & 0 deletions src/native/linux/qgslinuxnative.cpp
Expand Up @@ -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 )
{
Expand All @@ -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;
}
12 changes: 12 additions & 0 deletions src/native/linux/qgslinuxnative.h
Expand Up @@ -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
12 changes: 12 additions & 0 deletions src/native/qgsnative.cpp
Expand Up @@ -21,6 +21,11 @@
#include <QUrl>
#include <QFileInfo>

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

void QgsNative::initializeMainWindow( QWindow * )
{

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

}

QgsNative::NotificationResult QgsNative::showDesktopNotification( const QString &, const QString &, const NotificationSettings & )
{
NotificationResult result;
result.successful = false;
return result;
}
67 changes: 67 additions & 0 deletions src/native/qgsnative.h
Expand Up @@ -19,6 +19,8 @@
#define QGSNATIVE_H

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

class QString;
class QWindow;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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.