Пишем клиент для Slack с оповещениями

от автора

Приветствую, Хабравчане! Сегодня Slack выпустили свой клиент для Windows. Но еще совсем недавно такого клиента не было и необходимость получать нормальные уведомления была необходимостью. Slack предлагал использовать приложение Chrome. У данного подхода было два минуса:

  1. Отсутствие возможности настроить, сколько времени будет показываться уведомление
  2. Если уведомление пропало, то пользователь никак об этом не узнает.

К примеру, ты ушел налить себе кофе, а тут кто-то написал в чат. Возвращаешься на рабочее место и… тишина! Ничего не происходило. Ты работаешь дальше, а человек всё ждет и ждёт, пока кто-нибудь ему ответит. Непорядок! Skype вежливо уведомляет тебя всплывающим окошком и нагло сигнализирует в таскбаре о том, что тебе пришло сообщение. Быстрее прочти, а то твой таскбар так и будем мигать желтым светом. Даже если ты ушел на весь день.

Отмотаем время на 1 месяц назад. Идем на страницу myawesometeam.slack.com/apps и видим отсутствие нативного клиента для Windows и Linux, вместо этого там приложения для Chrome. Расстраиваемся. Запускаем приложение, понимаем всю печальность бытия.

Я начал искать решение проблемы. Первым нашелся SlackUI. Он построен на базе CEF (Chromium Embedded Framework). Я уже было почти обрадовался, запустил клиент и увидел всё то же самое, что и в приложении Chrome. Уведомления пропадают через 10 секунд, никаких уведомлений о том, что что-то было, пока ты ходил за кофе.

Что ж, я начал гуглить и наткнулся на то, что в Qt WebKit можно написать свой плагин, в том числе и для уведомлений. Нашелся проект QupZilla и его плагины для Qt WebKit (https://github.com/QupZilla/qtwebkit-plugins). Это было то, что нужно!

Этап 1. Делаем плагин уведомлений для Qt WebKit

В файле .pri нам нужно добавить заголовочные файлы для плагина и заголовочные файлы Qt, чтобы он мог подхватить наш плагин:

HEADERS += $$PWD/qtwebkitplugin.h \                $$[QT_INSTALL_HEADERS]/QtWebKit/qwebkitplatformplugin.h  SOURCES += $$PWD/qtwebkitplugin.cpp  DEFINES *= QT_STATICPLUGIN 

Код самого плагина:

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 

Пока что ничего сложного нет. NotificationPresenter — это некий класс, который будет отображать наши уведомления, пусть даже и консоль отладки:

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() << "--------------------------"; }  

Этап 2. Добавляем QWebView

Подключаем .pri файл к нашему проекту и добавляем в .pro файл зависимость от webkitwidgets:

... QT += webkitwidgets include(plugins/qtwebkit/qtwebkit-plugins.pri) ... 

Добавляем на какую-нибудь форму QWebView, после чего нам нужно его немного настроить и подписаться на событие featurePermissionRequested:

Еще код?

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);         }     } } 

Задаем тестовый url с уведомлением (например вот этот) и тестируем страницу. Должно показываться уведомление.

Этап 3. Добавляем куки и кэш на диск. Добавляем родные шрифты

В первую очередь бесят кривые шрифты. Поправим их сразу

webView->settings()->setFontFamily(QWebSettings::StandardFont, "Segoe UI"); webView->settings()->setFontSize(QWebSettings::DefaultFontSize, 16); 

Если мы теперь перезапустим приложение, то нам снова нужно повторить процедуру подтверждения, заново вводить пароли и т.п. Значит, наступило время для того, чтобы сохранять куки и кэш на диске.
С кэшем чуть проще:

void MainWindow::setStoragePath() {     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));     qDebug() << "Cache path" << path;      storagePath = path;     webView->page()->settings()->enablePersistentStorage(path); } 

С хранением куки посложнее. Я просто нашел готовое решение из примеров Qt. Можно легко найти в гугле по словам QWebView и CookieJar. Пример CookieJar можно найти в исходниках проекта

void MainWindow::setCookies() {     if (!cookieJar)     {         cookieJar = new CookieJar(this);     }      webView->page()->networkAccessManager()->setCookieJar(cookieJar); } 

После этого куки и кэш должны сохраняться и не нужно каждый раз вводить логины и пароли.

Этап 4. Подключаем дополнительные библиотеки

Для уведомлений я решил использовать Aseman Qt Tools.
Скачиваем, подключаем в .pro файл

include(asemantools/asemantools.pri) 

Теперь в NotificationPresenter нашего плагина нужно протащить некий интерфейс для отображения уведомлений.
Добавляем в qtwebkit.pri

INCLUDEPATH += $$top_srcdir HEADERS += $$top_srcdir/mainapplication.h 

Здесь MainApplication — наследник от QApplication. Добавляем функции отображения уведомлений:

notificationpresenter.cpp

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

mainapplication.h

#include <QApplication> #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); } 

Добавляем AsemanNotification в главное окно программы и заставляем таскбар мигать желтым светом:

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); // Мигаем таскбаром } 

Компилируем. Запускаем тестовую страницу. Должны появиться настоящие уведомления.

Этап 5. Пробуем собрать под линукс

И тут мы приехали. В Linux разные DE и все будет работать вверх тормашками.
В Unity иконка трея будет показываться в левом верхнем углу экрана. Выглядит это примерно так:

В Gnome 3 уведомления из AsemanTools у меня постоянно уползали куда-то за пределы экрана. Внятных решений я на нашел и снова за линукс стало грустно и обидно. Ничего не работает из коробки, нужно вечные пляски с бубном

Итоги

В результате получен опыт создания приложения на основе WebKit, а также создания плагинов для Qt.
Результат работы:

Ссылка на получившийся проект на Github

Настало время немного привести код в порядок, заварить кофе и вернуться к компьютеру, где радостно мигает желтый значок Slack в таскбаре. Запушить все изменения и ждать, когда радужный единорог проверит твой проект, чтобы ткнуть тебя головой в…

ссылка на оригинал статьи http://habrahabr.ru/post/253579/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *