diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e9de30d51..30ab234267 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -409,6 +409,8 @@ set(kwin_KDEINIT_SRCS xcbutils.cpp x11eventfilter.cpp logind.cpp + onscreennotification.cpp + osd.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 25c059de90..5cc6da4879 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -313,3 +313,21 @@ target_link_libraries(testScreenEdges add_test(kwin_testScreenEdges testScreenEdges) ecm_mark_as_test(testScreenEdges) + +######################################################## +# Test OnScreenNotification +######################################################## +set( testOnScreenNotification_SRCS + onscreennotificationtest.cpp + ../onscreennotification.cpp +) +add_executable( testOnScreenNotification ${testOnScreenNotification_SRCS}) + +target_link_libraries(testOnScreenNotification + Qt5::Test + Qt5::Quick + KF5::ConfigCore +) + +add_test(kwin-testOnScreenNotification testOnScreenNotification) +ecm_mark_as_test(testOnScreenNotification) diff --git a/autotests/onscreennotificationtest.cpp b/autotests/onscreennotificationtest.cpp new file mode 100644 index 0000000000..16024b350b --- /dev/null +++ b/autotests/onscreennotificationtest.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "onscreennotificationtest.h" +#include "../onscreennotification.h" + +#include <KSharedConfig> + +#include <QQmlEngine> +#include <QSignalSpy> +#include <QTest> + +QTEST_MAIN(OnScreenNotificationTest); + +using KWin::OnScreenNotification; + +void OnScreenNotificationTest::show() +{ + OnScreenNotification notification; + notification.setConfig(KSharedConfig::openConfig(QString(), KSharedConfig::SimpleConfig)); + notification.setEngine(new QQmlEngine(¬ification)); + notification.setMessage(QStringLiteral("Some text so that we see it in the test")); + + QSignalSpy visibleChangedSpy(¬ification, &OnScreenNotification::visibleChanged); + QCOMPARE(notification.isVisible(), false); + notification.setVisible(true); + QCOMPARE(notification.isVisible(), true); + QCOMPARE(visibleChangedSpy.count(), 1); + + // show again should not trigger + notification.setVisible(true); + QCOMPARE(visibleChangedSpy.count(), 1); + + // timer should not have hidden + QTest::qWait(500); + QCOMPARE(notification.isVisible(), true); + + // hide again + notification.setVisible(false); + QCOMPARE(notification.isVisible(), false); + QCOMPARE(visibleChangedSpy.count(), 2); + + // now show with timer + notification.setTimeout(250); + notification.setVisible(true); + QCOMPARE(notification.isVisible(), true); + QCOMPARE(visibleChangedSpy.count(), 3); + QVERIFY(visibleChangedSpy.wait()); + QCOMPARE(notification.isVisible(), false); + QCOMPARE(visibleChangedSpy.count(), 4); +} + +void OnScreenNotificationTest::timeout() +{ + OnScreenNotification notification; + QSignalSpy timeoutChangedSpy(¬ification, &OnScreenNotification::timeoutChanged); + QCOMPARE(notification.timeout(), 0); + notification.setTimeout(1000); + QCOMPARE(notification.timeout(), 1000); + QCOMPARE(timeoutChangedSpy.count(), 1); + notification.setTimeout(1000); + QCOMPARE(timeoutChangedSpy.count(), 1); + notification.setTimeout(0); + QCOMPARE(notification.timeout(), 0); + QCOMPARE(timeoutChangedSpy.count(), 2); +} + +void OnScreenNotificationTest::iconName() +{ + OnScreenNotification notification; + QSignalSpy iconNameChangedSpy(¬ification, &OnScreenNotification::iconNameChanged); + QVERIFY(iconNameChangedSpy.isValid()); + QCOMPARE(notification.iconName(), QString()); + notification.setIconName(QStringLiteral("foo")); + QCOMPARE(notification.iconName(), QStringLiteral("foo")); + QCOMPARE(iconNameChangedSpy.count(), 1); + notification.setIconName(QStringLiteral("foo")); + QCOMPARE(iconNameChangedSpy.count(), 1); + notification.setIconName(QStringLiteral("bar")); + QCOMPARE(notification.iconName(), QStringLiteral("bar")); + QCOMPARE(iconNameChangedSpy.count(), 2); +} + +void OnScreenNotificationTest::message() +{ + OnScreenNotification notification; + QSignalSpy messageChangedSpy(¬ification, &OnScreenNotification::messageChanged); + QVERIFY(messageChangedSpy.isValid()); + QCOMPARE(notification.message(), QString()); + notification.setMessage(QStringLiteral("foo")); + QCOMPARE(notification.message(), QStringLiteral("foo")); + QCOMPARE(messageChangedSpy.count(), 1); + notification.setMessage(QStringLiteral("foo")); + QCOMPARE(messageChangedSpy.count(), 1); + notification.setMessage(QStringLiteral("bar")); + QCOMPARE(notification.message(), QStringLiteral("bar")); + QCOMPARE(messageChangedSpy.count(), 2); +} + +#include "onscreennotificationtest.moc" diff --git a/autotests/onscreennotificationtest.h b/autotests/onscreennotificationtest.h new file mode 100644 index 0000000000..6f525ad668 --- /dev/null +++ b/autotests/onscreennotificationtest.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef ONSCREENNOTIFICATIONTEST_H +#define ONSCREENNOTIFICATIONTEST_H + +#include <QObject> + +class OnScreenNotificationTest : public QObject +{ + Q_OBJECT +private slots: + + void show(); + void timeout(); + void iconName(); + void message(); +}; + +#endif // ONSCREENNOTIFICATIONTEST_H diff --git a/onscreennotification.cpp b/onscreennotification.cpp new file mode 100644 index 0000000000..9bca931809 --- /dev/null +++ b/onscreennotification.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "onscreennotification.h" +#include <config-kwin.h> + +#include <QStandardPaths> +#include <QTimer> +#include <QQmlComponent> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQuickWindow> + +#include <KConfigGroup> + +#include <functional> + +using namespace KWin; + +OnScreenNotification::OnScreenNotification(QObject *parent) + : QObject(parent) + , m_timer(new QTimer(this)) +{ + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, std::bind(&OnScreenNotification::setVisible, this, false)); + connect(this, &OnScreenNotification::visibleChanged, this, + [this] { + if (m_visible) { + show(); + } else { + m_timer->stop(); + } + } + ); +} + +OnScreenNotification::~OnScreenNotification() +{ + if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem.data())) { + w->hide(); + w->destroy(); + } +} + +void OnScreenNotification::setConfig(KSharedConfigPtr config) +{ + m_config = config; +} + +void OnScreenNotification::setEngine(QQmlEngine *engine) +{ + m_qmlEngine = engine; +} + +bool OnScreenNotification::isVisible() const +{ + return m_visible; +} + +void OnScreenNotification::setVisible(bool visible) +{ + if (m_visible == visible) { + return; + } + m_visible = visible; + emit visibleChanged(); +} + +QString OnScreenNotification::message() const +{ + return m_message; +} + +void OnScreenNotification::setMessage(const QString &message) +{ + if (m_message == message) { + return; + } + m_message = message; + emit messageChanged(); +} + +QString OnScreenNotification::iconName() const +{ + return m_iconName; +} + +void OnScreenNotification::setIconName(const QString &iconName) +{ + if (m_iconName == iconName) { + return; + } + m_iconName = iconName; + emit iconNameChanged(); +} + +int OnScreenNotification::timeout() const +{ + return m_timer->interval(); +} + +void OnScreenNotification::setTimeout(int timeout) +{ + if (m_timer->interval() == timeout) { + return; + } + m_timer->setInterval(timeout); + emit timeoutChanged(); +} + +void OnScreenNotification::show() +{ + Q_ASSERT(m_visible); + ensureQmlContext(); + ensureQmlComponent(); + if (m_timer->interval() != 0) { + m_timer->start(); + } +} + +void OnScreenNotification::ensureQmlContext() +{ + Q_ASSERT(m_qmlEngine); + if (!m_qmlContext.isNull()) { + return; + } + m_qmlContext.reset(new QQmlContext(m_qmlEngine)); + m_qmlContext->setContextProperty(QStringLiteral("osd"), this); +} + +void OnScreenNotification::ensureQmlComponent() +{ + Q_ASSERT(m_config); + Q_ASSERT(m_qmlEngine); + if (!m_qmlComponent.isNull()) { + return; + } + m_qmlComponent.reset(new QQmlComponent(m_qmlEngine)); + const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + m_config->group(QStringLiteral("OnScreenNotification")).readEntry("QmlPath", QStringLiteral(KWIN_NAME "/onscreennotification/plasma/main.qml"))); + if (fileName.isEmpty()) { + return; + } + m_qmlComponent->loadUrl(QUrl::fromLocalFile(fileName)); + if (!m_qmlComponent->isError()) { + m_mainItem.reset(m_qmlComponent->create(m_qmlContext.data())); + } else { + m_qmlComponent.reset(); + } +} + +#include "onscreennotification.moc" diff --git a/onscreennotification.h b/onscreennotification.h new file mode 100644 index 0000000000..40a67e1592 --- /dev/null +++ b/onscreennotification.h @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef KWIN_ONSCREENNOTIFICATION_H +#define KWIN_ONSCREENNOTIFICATION_H + +#include <QObject> + +#include <KSharedConfig> + +class QTimer; +class QQmlContext; +class QQmlComponent; +class QQmlEngine; + +namespace KWin { + +class OnScreenNotification : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) + Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) + Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged) + Q_PROPERTY(int timeout READ timeout WRITE setTimeout NOTIFY timeoutChanged) + +public: + explicit OnScreenNotification(QObject *parent = nullptr); + ~OnScreenNotification() override; + bool isVisible() const; + QString message() const; + QString iconName() const; + int timeout() const; + + void setVisible(bool m_visible); + void setMessage(const QString &message); + void setIconName(const QString &iconName); + void setTimeout(int timeout); + + void setConfig(KSharedConfigPtr config); + void setEngine(QQmlEngine *engine); + +Q_SIGNALS: + void visibleChanged(); + void messageChanged(); + void iconNameChanged(); + void timeoutChanged(); + +private: + void show(); + void ensureQmlContext(); + void ensureQmlComponent(); + bool m_visible = false; + QString m_message; + QString m_iconName; + QTimer *m_timer; + KSharedConfigPtr m_config; + QScopedPointer<QQmlContext> m_qmlContext; + QScopedPointer<QQmlComponent> m_qmlComponent; + QQmlEngine *m_qmlEngine = nullptr; + QScopedPointer<QObject> m_mainItem; +}; +} + +#endif // KWIN_ONSCREENNOTIFICATION_H diff --git a/osd.cpp b/osd.cpp new file mode 100644 index 0000000000..8d2ec307c1 --- /dev/null +++ b/osd.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include "osd.h" +#include "onscreennotification.h" +#include "main.h" +#include "workspace.h" +#include "scripting/scripting.h" + +#include <QQmlEngine> + +namespace KWin +{ +namespace OSD +{ + +static OnScreenNotification *create() +{ + auto osd = new OnScreenNotification(workspace()); + osd->setConfig(kwinApp()->config()); + osd->setEngine(Scripting::self()->qmlEngine()); + return osd; +} + +static OnScreenNotification *osd() +{ + static OnScreenNotification *s_osd = create(); + return s_osd; +} + +void show(const QString &message, const QString &iconName, int timeout) +{ + if (!kwinApp()->shouldUseWaylandForCompositing()) { + // FIXME: only supported on Wayland + return; + } + auto notification = osd(); + notification->setIconName(iconName); + notification->setMessage(message); + notification->setTimeout(timeout); + notification->setVisible(true); +} + +void show(const QString &message, int timeout) +{ + show(message, QString(), timeout); +} + +void show(const QString &message, const QString &iconName) +{ + show(message, iconName, 0); +} + +void hide() +{ + if (!kwinApp()->shouldUseWaylandForCompositing()) { + // FIXME: only supported on Wayland + return; + } + osd()->setVisible(false); +} + +} +} diff --git a/osd.h b/osd.h new file mode 100644 index 0000000000..234ca65c5c --- /dev/null +++ b/osd.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef KWIN_OSD_H +#define KWIN_OSD_H + +#include <QString> + +namespace KWin +{ +namespace OSD +{ + +void show(const QString &message, const QString &iconName = QString()); +void show(const QString &message, int timeout); +void show(const QString &message, const QString &iconName, int timeout); +void hide(); + +} +} + +#endif diff --git a/qml/CMakeLists.txt b/qml/CMakeLists.txt index 4faff91516..4a0d1af232 100644 --- a/qml/CMakeLists.txt +++ b/qml/CMakeLists.txt @@ -1,2 +1,3 @@ install( DIRECTORY outline/plasma DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/outline ) +install( DIRECTORY onscreennotification/plasma DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/onscreennotification ) install( DIRECTORY virtualkeyboard DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME} ) diff --git a/qml/onscreennotification/plasma/dummydata/osd.qml b/qml/onscreennotification/plasma/dummydata/osd.qml new file mode 100644 index 0000000000..b7a17c2c39 --- /dev/null +++ b/qml/onscreennotification/plasma/dummydata/osd.qml @@ -0,0 +1,7 @@ +import QtQuick 2.3 + +QtObject { + property bool visible: true + property string message: "This is an example message.\nUsing multiple lines" + property string iconName: "kwin" +} diff --git a/qml/onscreennotification/plasma/main.qml b/qml/onscreennotification/plasma/main.qml new file mode 100644 index 0000000000..999ba9c3b8 --- /dev/null +++ b/qml/onscreennotification/plasma/main.qml @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Martin Graesslin <mgraesslin@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import QtQuick 2.0; +import QtQuick.Window 2.0; +import org.kde.plasma.core 2.0 as PlasmaCore; +import org.kde.plasma.components 2.0 as Plasma; +import QtQuick.Layouts 1.3; + +PlasmaCore.Dialog { + id: dialog + location: PlasmaCore.Types.Floating + visible: osd.visible + flags: Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint + type: PlasmaCore.Dialog.OnScreenDisplay + outputOnly: true + + mainItem: RowLayout { + PlasmaCore.IconItem { + Layout.minimumWidth: 64 + Layout.minimumHeight: 64 + source: osd.iconName + visible: osd.iconName != "" + } + Plasma.Label { + text: osd.message + } + } +}