We are writing a client for Slack with notifications

    Greetings, Khabravchans! Today Slack released their client for Windows. But more recently, such a client was not and the need to receive normal notifications was a necessity. Slack suggested using the Chrome app. This approach had two drawbacks:
    1. Inability to customize how long a notification will be displayed
    2. If the notification is lost, the user will not know about it in any way.



    For example, you left to pour yourself coffee, and then someone wrote in a chat. You return to the workplace and ... silence! Nothing happened. You work further, and the person waits and waits for someone to answer him. Mess! Skype politely notifies you with a pop-up window and brazenly signals in the taskbar that you received a message. Read it faster, otherwise your taskbar will blink yellow. Even if you left the whole day.

    We rewind time 1 month ago. We go to myawesometeam.slack.com/apps page and see the absence of a native client for Windows and Linux, instead there are applications for Chrome. We are upset. We launch the application, we understand the sadness of being.

    I started looking for a solution to the problem. The first was SlackUI. It is built on the basis of CEF (Chromium Embedded Framework). I was almost happy, I launched the client and saw everything the same as in the Chrome application. Notifications disappear after 10 seconds, no notifications that there was something while you went for coffee.

    Well, I started googling and came across the fact that in Qt WebKit you can write your own plugin, including for notifications. There was a QupZilla project and its plugins for Qt WebKit ( https://github.com/QupZilla/qtwebkit-plugins ). That was what you needed!

    Stage 1. Making a notification plugin for Qt WebKit


    In the .pri file, we need to add header files for the plugin and Qt header files so that it can pick up our plugin:
    HEADERS += $$PWD/qtwebkitplugin.h \
                   $$[QT_INSTALL_HEADERS]/QtWebKit/qwebkitplatformplugin.h
    SOURCES += $$PWD/qtwebkitplugin.cpp
    DEFINES *= QT_STATICPLUGIN
    

    The code of the plugin itself:
    qtwebkitplugin.h
    #include "qwebkitplatformplugin.h"
    class QtWebKitPlugin : public QObject, public QWebKitPlatformPlugin
    {
        Q_OBJECT
        Q_INTERFACES(QWebKitPlatformPlugin)
    #if QT_VERSION >= 0x050000
        Q_PLUGIN_METADATA(IID "org.qtwebkit.QtWebKit.QtWebKitPlugins")
    #endif
    public:
        explicit QtWebKitPlugin();
        bool supportsExtension(Extension ext) const;
        QObject* createExtension(Extension ext) const;
    };
    



    qtwebkitplugin.cpp
    bool QtWebKitPlugin::supportsExtension(Extension ext) const
    {
        return (ext == Notifications);
    }
    QObject* QtWebKitPlugin::createExtension(Extension ext) const
    {
        switch (ext) {
        case Notifications:
            return new NotificationPresenter();
        default:
            return 0;
        }
    }
    #if QT_VERSION < 0x050000
    Q_EXPORT_PLUGIN2(qtwebkitplugins, QtWebKitPlugin)
    #endif
    #if (QT_VERSION < 0x050000)
    Q_IMPORT_PLUGIN(qtwebkitplugins)
    #else
    Q_IMPORT_PLUGIN(QtWebKitPlugin)
    #endif
    



    So far, there is nothing complicated. NotificationPresenter is a class that will display our notifications, even if it’s a debugging console:

    notificationpresenter.h
    #include "qwebkitplatformplugin.h"
    class NotificationPresenter : public QWebNotificationPresenter
    {
    public:
        explicit NotificationPresenter();
        void showNotification(const QWebNotificationData* data);
    };
    


    notificationpresenter.cpp
    NotificationPresenter::NotificationPresenter()
        : QWebNotificationPresenter()
    {
    }
    void NotificationPresenter::showNotification(const QWebNotificationData* data)
    {
        qDebug() << "--------------------------";
        qDebug() << "Title:";
        qDebug() << data->title();
        qDebug() << "Message:";
        qDebug() << data->message();
        qDebug() << "--------------------------";
    }
    



    Stage 2. Add QWebView


    We connect the .pri file to our project and add the dependency on webkitwidgets to the .pro file:
    ...
    QT += webkitwidgets
    include(plugins/qtwebkit/qtwebkit-plugins.pri)
    ...
    


    Add a QWebView to some form, after which we need to configure it a bit and subscribe to the featurePermissionRequested event:

    Another code?
    void MainWindow::createWebView()
    {
        webview->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
        webview->settings()->setAttribute(QWebSettings::NotificationsEnabled, true);
        connect(webView->page(), SIGNAL(featurePermissionRequested(QWebFrame*,QWebPage::Feature)),
                this, SLOT(featureRequest(QWebFrame*,QWebPage::Feature)));
    }
    void MainWindow::featureRequest(QWebFrame *frame, QWebPage::Feature feature)
    {
        qDebug() << frame->url();
        if (feature == QWebPage::Feature::Notifications)
        {
            int result = QMessageBox::question(this,
                                  QString("Notification permission"),
                                  QString("%1\nasks for notifications persmission. Should I allow?").arg(frame->url().toString()),
                                  QMessageBox::StandardButton::Ok, QMessageBox::Cancel);
            if (result == QMessageBox::StandardButton::Ok)
            {
                webView->page()->setFeaturePermission(frame, feature,
                                                      QWebPage::PermissionPolicy::PermissionGrantedByUser);
            }
        }
    }
    



    We set a test url with a notification ( for example, this one ) and test the page. A notification should be displayed.

    Step 3. Add cookies and cache to disk. Add native fonts


    First of all, curved fonts are furious. Fix them right away
    webView->settings()->setFontFamily(QWebSettings::StandardFont, "Segoe UI");
    webView->settings()->setFontSize(QWebSettings::DefaultFontSize, 16);
    


    If we now restart the application, then again we need to repeat the confirmation procedure, re-enter the passwords, etc. So it's time to save cookies and cache on disk.
    With a cache a little easier:
    void MainWindow::setStoragePath()
    {
        QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
        qDebug() << "Cache path" << path;
        storagePath = path;
        webView->page()->settings()->enablePersistentStorage(path);
    }
    

    Cookies are more complicated. I just found a ready-made solution from Qt examples. You can easily find it on Google according to QWebView and CookieJar. CookieJar example can be found in the project sources
    void MainWindow::setCookies()
    {
        if (!cookieJar)
        {
            cookieJar = new CookieJar(this);
        }
        webView->page()->networkAccessManager()->setCookieJar(cookieJar);
    }
    

    After that, cookies and cache should be saved and you do not need to enter logins and passwords every time.

    Stage 4. We connect additional libraries


    For notifications, I decided to use Aseman Qt Tools .
    Download, connect to .pro file
    include(asemantools/asemantools.pri)
    


    Now in the NotificationPresenter of our plugin you need to drag a certain interface to display notifications.
    Add to qtwebkit.pri
    INCLUDEPATH += $$top_srcdir
    HEADERS += $$top_srcdir/mainapplication.h
    


    Here, MainApplication is a descendant of QApplication. Add notification display functions:

    notificationpresenter.cpp
    void NotificationPresenter::showNotification(const QWebNotificationData* data)
    {
        mApp->showNotification(data->title(), data->message());
    }
    


    mainapplication.h
    #include 
    #include "mainwindow.h"
    #define mApp ((MainApplication*)MainApplication::instance())
    class MainApplication : public QApplication
    {
        Q_OBJECT
    public:
        explicit MainApplication(int &argc, char** argv);
        void setMainWindow(MainWindow* window);
        MainWindow* getMainWindow();
        void showNotification(QString title, QString message);
        ~MainApplication();
    private:
        MainWindow *m_window = 0;
    };
    



    mainapplication.cpp
    MainWindow *MainApplication::getMainWindow()
    {
        if (!m_window){
            m_window = new MainWindow();
        }
        return m_window;
    }
    void MainApplication::showNotification(QString title, QString message)
    {
        getMainWindow()->showNotification(title, message);
    }
    



    Add AsemanNotification to the main program window and make the taskbar blink yellow:
    MainWindow::MainWindow() {
        // ...
        notification = new AsemanNativeNotification(this);
        // ...
    }
    void MainWindow::showNotification(QString title, QString message)
    {
        notification->sendNotify(title, message, "://images/png/Slack.png", 0, 100000); // Показываем уведомление
        QApplication::alert(this); // Мигаем таскбаром
    }
    

    We compile. We launch the test page. These notifications should appear.

    Stage 5. We try to collect under Linux


    And then we arrived. Linux has different DEs and everything will work upside down.
    In Unity, the tray icon will be shown in the upper left corner of the screen. It looks something like this:

    In Gnome 3, notifications from AsemanTools constantly crawled somewhere off the screen. I didn’t find any clear solutions and again for Linux it became sad and insulting. Nothing works out of the box, you need eternal dancing with a tambourine

    Summary


    As a result, we gained experience creating an application based on WebKit, as well as creating plugins for Qt.
    Result of work:


    Link to the resulting project on Github

    It is time to tidy up the code a bit, make coffee and return to the computer, where the yellow Slack icon in the taskbar blinks joyfully. Launch all the changes and wait for the rainbow unicorn to check your project in order to poke your head at ...

    Also popular now: