Приветствую, читатели! Сегодня 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
Код самого плагина:
#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;
};
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 — это некий класс, который будет отображать наши уведомления, пусть даже и консоль отладки:
#include "qwebkitplatformplugin.h"
class NotificationPresenter : public QWebNotificationPresenter
{
public:
explicit NotificationPresenter();
void showNotification(const QWebNotificationData* data);
};
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("%1nasks 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. Добавляем функции отображения уведомлений:
void NotificationPresenter::showNotification(const QWebNotificationData* data)
{
mApp->showNotification(data->title(), data->message());
}
#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;
};
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 в таскбаре. Запушить все изменения и ждать, когда радужный единорог проверит твой проект, чтобы ткнуть тебя головой в…
Автор: snegovikufa