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(&notification));
+    notification.setMessage(QStringLiteral("Some text so that we see it in the test"));
+
+    QSignalSpy visibleChangedSpy(&notification, &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(&notification, &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(&notification, &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(&notification, &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
+        }
+    }
+}