Пишем клиент для Slack с оповещениями
Приветствую, Хабравчане! Сегодня Slack выпустили свой клиент для Windows. Но еще совсем недавно такого клиента не было и необходимость получать нормальные уведомления была необходимостью. Slack предлагал использовать приложение Chrome. У данного подхода было два минуса: Отсутствие возможности настроить, сколько времени будет показываться уведомление Если уведомление пропало, то пользователь никак об этом не узнает. К примеру, ты ушел налить себе кофе, а тут кто-то написал в чат. Возвращаешься на рабочее место и… тишина! Ничего не происходило. Ты работаешь дальше, а человек всё ждет и ждёт, пока кто-нибудь ему ответит. Непорядок! 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 >= 0×050000 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
#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 в таскбаре. Запушить все изменения и ждать, когда радужный единорог проверит твой проект, чтобы ткнуть тебя головой в…