diff --git a/repos/gems/recipes/src/test-tiled_wm/content.mk b/repos/gems/recipes/src/test-tiled_wm/content.mk
new file mode 100644
index 0000000000..3bf4ad64d8
--- /dev/null
+++ b/repos/gems/recipes/src/test-tiled_wm/content.mk
@@ -0,0 +1,18 @@
+MIRROR_FROM_REP_DIR := src/test/tiled_wm
+
+content: $(MIRROR_FROM_REP_DIR) LICENSE
+
+$(MIRROR_FROM_REP_DIR):
+ $(mirror_from_rep_dir)
+
+MIRROR_FROM_LIBPORTS := src/app/qt5/tmpl/target_defaults.inc \
+ src/app/qt5/tmpl/target_final.inc
+
+content: $(MIRROR_FROM_LIBPORTS)
+
+$(MIRROR_FROM_LIBPORTS):
+ mkdir -p $(dir $@)
+ cp -r $(GENODE_DIR)/repos/libports/$@ $(dir $@)
+
+LICENSE:
+ cp $(GENODE_DIR)/LICENSE $@
diff --git a/repos/gems/recipes/src/test-tiled_wm/hash b/repos/gems/recipes/src/test-tiled_wm/hash
new file mode 100644
index 0000000000..d3ce74600a
--- /dev/null
+++ b/repos/gems/recipes/src/test-tiled_wm/hash
@@ -0,0 +1 @@
+2018-10-02 0741bbe9067957b8884dfc14708e0dbcce6f5285
diff --git a/repos/gems/recipes/src/test-tiled_wm/used_apis b/repos/gems/recipes/src/test-tiled_wm/used_apis
new file mode 100644
index 0000000000..a73c52e02d
--- /dev/null
+++ b/repos/gems/recipes/src/test-tiled_wm/used_apis
@@ -0,0 +1,13 @@
+base
+libc
+os
+qoost
+qt5_core
+qt5_gui
+qt5_qjpeg
+qt5_qpa_nitpicker
+qt5_widgets
+report_session
+stdcxx
+timer_session
+vfs
diff --git a/repos/gems/run/tiled_wm.run b/repos/gems/run/tiled_wm.run
new file mode 100644
index 0000000000..91cb43ebc9
--- /dev/null
+++ b/repos/gems/run/tiled_wm.run
@@ -0,0 +1,248 @@
+source ${genode_dir}/repos/libports/run/qt5_common.inc
+
+import_from_depot [depot_user]/src/qt5_component \
+ [depot_user]/src/qt5_printsupport \
+ [depot_user]/src/qt5_textedit \
+ [depot_user]/src/qt5_widgets \
+ [depot_user]/src/dynamic_rom \
+ [depot_user]/src/test-tiled_wm
+
+if {[have_spec odroid_xu]} {
+ puts "Run script does not support this platform."
+ exit 0
+}
+
+#
+# Generate config
+#
+
+proc qt5_layouter_config { } {
+ return { }
+}
+
+proc qt5_decorator_binary { } { return "themed_decorator" }
+
+proc qt5_decorator_config { } {
+ return {
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+}
+
+append config {
+
+ } [qt5_parent_provides feature] {
+
+
+
+
+ } [qt5_start_nodes feature] {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2018-01-01 00:01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2018-01-01 00:01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2018-01-01 00:01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2018-01-01 00:01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2018-01-01 00:01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+append build_components [qt5_build_components feature]
+
+# for debugging only
+#append build_components { app/window_layouter app/themed_decorator }
+
+build $build_components
+
+append boot_modules [qt5_boot_modules feature]
+
+# for debugging only
+#append boot_modules { window_layouter themed_decorator }
+
+build_boot_image $boot_modules
+
+append qemu_args " -usbdevice tablet "
+
+run_genode_until forever
+
+# vi: set ft=tcl :
diff --git a/repos/gems/src/test/tiled_wm/app/app.cpp b/repos/gems/src/test/tiled_wm/app/app.cpp
new file mode 100644
index 0000000000..486d31a330
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/app.cpp
@@ -0,0 +1,45 @@
+/*
+ * \brief Tiled-WM test: example application widget
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Qt includes */
+#include
+#include
+
+/* local includes */
+#include "app.h"
+
+
+App::App(QString name)
+{
+ _name->setText("This is " + name + " an example application for the tiled-WM test.");
+ _entry->setPlaceholderText("Placeholder text");
+
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_name);
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_entry);
+ _layout->addWidget(new Spacer(), 1);
+
+ for (int i = 0; i < 3; ++i) {
+ QLabel *l = new QLabel(QString("QLabel No." + QString::number(i)));
+ l->setToolTip(QString::number(i) + " is just a number.");
+ _layout->addWidget(l);
+ }
+
+ _layout->addWidget(new Spacer(), 1);
+}
+
+
+App::~App()
+{
+}
diff --git a/repos/gems/src/test/tiled_wm/app/app.h b/repos/gems/src/test/tiled_wm/app/app.h
new file mode 100644
index 0000000000..328bd8a2a8
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/app.h
@@ -0,0 +1,46 @@
+/*
+ * \brief TIled-WM test: example application widget
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TEST__TILED_WM__APP__APP_H_
+#define _TEST__TILED_WM__APP__APP_H_
+
+/* Qt includes */
+#include
+#include
+#include
+#include
+
+/* Qoost includes */
+#include
+#include
+
+/* local includes */
+#include
+
+
+class App : public Compound_widget
+{
+ Q_OBJECT
+
+ private:
+
+ QMember _name;
+ QMember _entry;
+
+ public:
+
+ App(QString name);
+ ~App();
+};
+
+#endif /* _TEST__TILED_WM__APP__APP_H_ */
diff --git a/repos/gems/src/test/tiled_wm/app/app.pro b/repos/gems/src/test/tiled_wm/app/app.pro
new file mode 100644
index 0000000000..70756a5000
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/app.pro
@@ -0,0 +1,6 @@
+TEMPLATE = app
+TARGET = test-tiled_wm-app
+QT = core gui widgets
+SOURCES += main.cpp app.cpp
+HEADERS += app.h ../util.h
+RESOURCES = app.qrc
diff --git a/repos/gems/src/test/tiled_wm/app/app.qrc b/repos/gems/src/test/tiled_wm/app/app.qrc
new file mode 100644
index 0000000000..cb8c387b6f
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/app.qrc
@@ -0,0 +1,8 @@
+
+
+
+ ../style.qss
+
+
+
+
diff --git a/repos/gems/src/test/tiled_wm/app/main.cpp b/repos/gems/src/test/tiled_wm/app/main.cpp
new file mode 100644
index 0000000000..275a3a8109
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/main.cpp
@@ -0,0 +1,55 @@
+/*
+ * \brief Tiled-WM test: example application
+ * \author Christian Helmuth
+ * \date 2018-09-26
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Genode includes */
+#include
+
+/* local includes */
+#include
+#include "app.h"
+
+
+struct Main
+{
+ Libc::Env &env;
+
+ Genode::Attached_rom_dataspace config { env, "config" };
+
+ QApplication &app { qt5_initialization(env) };
+
+ QMember widget { name_from_config() };
+
+ QString name_from_config()
+ {
+ Name name = config.xml().attribute_value("name", Name("no name"));
+
+ return QString(name.string());
+ }
+
+ Main(Libc::Env &env) : env(env)
+ {
+ widget->show();
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env)
+{
+ Libc::with_libc([&] {
+
+ static Main main { env };
+
+ exit(main.app.exec());
+ });
+}
+
diff --git a/repos/gems/src/test/tiled_wm/app/target.mk b/repos/gems/src/test/tiled_wm/app/target.mk
new file mode 100644
index 0000000000..48ac9307d3
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/app/target.mk
@@ -0,0 +1 @@
+include $(PRG_DIR)/../target.inc
diff --git a/repos/gems/src/test/tiled_wm/manager/main.cc b/repos/gems/src/test/tiled_wm/manager/main.cc
new file mode 100644
index 0000000000..c9a14a1e0f
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/manager/main.cc
@@ -0,0 +1,211 @@
+/*
+ * \brief Tiled-WM test: GUI manager
+ * \author Christian Helmuth
+ * \date 2018-09-26
+ *
+ * GUI manager implements the user-visible display state machine.
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+
+typedef Genode::String<32> Name;
+
+
+namespace Test { struct Manager; }
+
+struct Test::Manager
+{
+ Genode::Env &env;
+
+ Genode::Attached_rom_dataspace content_request_rom { env, "content_request" };
+ Genode::Attached_rom_dataspace overlay_request_rom { env, "overlay_request" };
+
+ Genode::Signal_handler content_request_handler {
+ env.ep(), *this, &Manager::handle_content_request };
+ Genode::Signal_handler overlay_request_handler {
+ env.ep(), *this, &Manager::handle_overlay_request };
+
+ Genode::Reporter apps_report { env, "apps" };
+ Genode::Reporter overlay_report { env, "overlay" };
+
+ Genode::Reporter layout_rules_report { env, "rules", "layout_rules" };
+
+ struct App {
+ char const *label;
+ char const *name;
+ bool visible;
+ } apps[3] {
+ { "test-tiled_wm-app-1", "app1", true },
+ { "test-tiled_wm-app-2", "app2", false },
+ { "textedit", "textedit", false }
+ };
+
+ bool overlay_visible { false };
+
+ void report_apps();
+ void report_overlay();
+ void report_layout_rules();
+
+ void handle_content_request();
+ void handle_overlay_request();
+
+ Manager(Genode::Env &env);
+};
+
+
+void Test::Manager::handle_content_request()
+{
+ content_request_rom.update();
+
+ Name requested_app =
+ content_request_rom.xml().attribute_value("name", Name());
+
+ if (!requested_app.valid()) return;
+
+ App *found = nullptr;
+ for (App &app : apps) {
+ if (requested_app != app.name) continue;
+ found = &app;
+ break;
+ }
+ if (!found || found->visible) return;
+
+ for (App &app : apps) {
+ if (&app == found) {
+ app.visible = true;
+ } else {
+ app.visible = false;
+ }
+ }
+
+ report_apps();
+ report_layout_rules();
+}
+
+
+void Test::Manager::handle_overlay_request()
+{
+ overlay_request_rom.update();
+
+ bool const request_visible =
+ overlay_request_rom.xml().attribute_value("visible", false);
+
+ if (request_visible == overlay_visible) return;
+
+ overlay_visible = request_visible;
+
+ report_overlay();
+ report_layout_rules();
+}
+
+
+void Test::Manager::report_apps()
+{
+ Genode::Reporter::Xml_generator xml(apps_report, [&] () {
+ for (App &app : apps) {
+ xml.node("app", [&] () {
+ xml.attribute("name", app.name);
+ xml.attribute("visible", app.visible);
+ });
+ }
+ });
+}
+
+
+void Test::Manager::report_overlay()
+{
+ Genode::Reporter::Xml_generator xml(overlay_report, [&] () {
+ xml.attribute("visible", overlay_visible);
+ });
+}
+
+
+void Test::Manager::report_layout_rules()
+{
+ Genode::Reporter::Xml_generator xml(layout_rules_report, [&] () {
+ xml.node("screen", [&] () {
+ xml.node("column", [&] () {
+ xml.attribute("name", "screen");
+ xml.attribute("layer", "1");
+ xml.node("row", [&] () {
+ xml.attribute("name", "content");
+ xml.attribute("layer", "4");
+ xml.node("column", [&] () {
+ xml.attribute("weight", "2");
+ });
+ xml.node("column", [&] () {
+ xml.attribute("name", "overlay");
+ xml.attribute("layer", "3");
+ xml.attribute("weight", "1");
+ });
+ });
+ xml.node("row", [&] () {
+ xml.attribute("name", "panel");
+ xml.attribute("layer", "2");
+ xml.attribute("height", "24");
+ });
+ });
+ });
+ xml.node("assign", [&] () {
+ xml.attribute("label_prefix", "test-tiled_wm-panel");
+ xml.attribute("target", "panel");
+ });
+ if (overlay_visible) {
+ xml.node("assign", [&] () {
+ xml.attribute("label_prefix", "test-tiled_wm-overlay");
+ xml.attribute("target", "overlay");
+ });
+ }
+
+ /* debug */
+ if (false) {
+ xml.node("assign", [&] () {
+ xml.attribute("label_prefix", "");
+ xml.attribute("target", "screen");
+ xml.attribute("xpos", "any");
+ xml.attribute("ypos", "any");
+ });
+ }
+
+ for (App &app : apps) {
+ if (!app.visible) continue;
+
+ xml.node("assign", [&] () {
+ xml.attribute("label_prefix", app.label);
+ xml.attribute("target", "content");
+ });
+ break;
+ }
+ });
+}
+
+
+Test::Manager::Manager(Genode::Env &env) : env(env)
+{
+ apps_report.enabled(true);
+ overlay_report.enabled(true);
+ layout_rules_report.enabled(true);
+
+ content_request_rom.sigh(content_request_handler);
+ overlay_request_rom.sigh(overlay_request_handler);
+
+ report_apps();
+ report_overlay();
+ report_layout_rules();
+}
+
+
+void Component::construct(Genode::Env &env) { static Test::Manager manager(env); }
diff --git a/repos/gems/src/test/tiled_wm/manager/target.mk b/repos/gems/src/test/tiled_wm/manager/target.mk
new file mode 100644
index 0000000000..de4d67ed8b
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/manager/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-tiled_wm-manager
+SRC_CC = main.cc
+LIBS = base
diff --git a/repos/gems/src/test/tiled_wm/overlay/main.cpp b/repos/gems/src/test/tiled_wm/overlay/main.cpp
new file mode 100644
index 0000000000..4889626bcb
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * \brief Tiled-WM test: example overlay
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* local includes */
+#include
+#include "overlay.h"
+
+
+struct Main
+{
+ Libc::Env &env;
+
+ QApplication &app { qt5_initialization(env) };
+
+ QMember widget { };
+
+ Main(Libc::Env &env) : env(env)
+ {
+ widget->show();
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env)
+{
+ Libc::with_libc([&] {
+
+ static Main main { env };
+
+ exit(main.app.exec());
+ });
+}
+
diff --git a/repos/gems/src/test/tiled_wm/overlay/overlay.cpp b/repos/gems/src/test/tiled_wm/overlay/overlay.cpp
new file mode 100644
index 0000000000..3acad97358
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/overlay.cpp
@@ -0,0 +1,37 @@
+/*
+ * \brief Tiled-WM test: overlay widget
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Qt includes */
+#include
+#include
+
+/* local includes */
+#include "overlay.h"
+
+
+Overlay::Overlay()
+{
+ _label->setText("WiFi overlay");
+ _entry->setEchoMode(QLineEdit::Password);
+
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_label);
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_entry);
+ _layout->addWidget(new Spacer(), 1);
+}
+
+
+Overlay::~Overlay()
+{
+}
diff --git a/repos/gems/src/test/tiled_wm/overlay/overlay.h b/repos/gems/src/test/tiled_wm/overlay/overlay.h
new file mode 100644
index 0000000000..ac880c498d
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/overlay.h
@@ -0,0 +1,46 @@
+/*
+ * \brief Tiled-WM test: overlay widget
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TEST__TILED_WM__OVERLAY__OVERLAY_H_
+#define _TEST__TILED_WM__OVERLAY__OVERLAY_H_
+
+/* Qt includes */
+#include
+#include
+#include
+#include
+
+/* Qoost includes */
+#include
+#include
+
+/* local includes */
+#include
+
+
+class Overlay : public Compound_widget
+{
+ Q_OBJECT
+
+ private:
+
+ QMember _label;
+ QMember _entry;
+
+ public:
+
+ Overlay();
+ ~Overlay();
+};
+
+#endif /* _TEST__TILED_WM__OVERLAY__OVERLAY_H_ */
diff --git a/repos/gems/src/test/tiled_wm/overlay/overlay.pro b/repos/gems/src/test/tiled_wm/overlay/overlay.pro
new file mode 100644
index 0000000000..2635ce4a9e
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/overlay.pro
@@ -0,0 +1,6 @@
+TEMPLATE = app
+TARGET = test-tiled_wm-overlay
+QT = core gui widgets
+SOURCES += main.cpp overlay.cpp
+HEADERS += overlay.h ../util.h
+RESOURCES = overlay.qrc
diff --git a/repos/gems/src/test/tiled_wm/overlay/overlay.qrc b/repos/gems/src/test/tiled_wm/overlay/overlay.qrc
new file mode 100644
index 0000000000..cb8c387b6f
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/overlay.qrc
@@ -0,0 +1,8 @@
+
+
+
+ ../style.qss
+
+
+
+
diff --git a/repos/gems/src/test/tiled_wm/overlay/target.mk b/repos/gems/src/test/tiled_wm/overlay/target.mk
new file mode 100644
index 0000000000..48ac9307d3
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/overlay/target.mk
@@ -0,0 +1 @@
+include $(PRG_DIR)/../target.inc
diff --git a/repos/gems/src/test/tiled_wm/panel/main.cpp b/repos/gems/src/test/tiled_wm/panel/main.cpp
new file mode 100644
index 0000000000..75342881b6
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/main.cpp
@@ -0,0 +1,45 @@
+/*
+ * \brief Tiled-WM test: panel
+ * \author Christian Helmuth
+ * \date 2018-09-26
+ *
+ * Panel is a Qt5-based example panel at the bottom of the screen.
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* local includes */
+#include "panel.h"
+
+
+struct Main
+{
+ Libc::Env &env;
+
+ Genode_signal_dispatcher dispatcher { env };
+
+ QApplication &app { qt5_initialization(env) };
+
+ QMember widget { env, dispatcher.signal_receiver() };
+
+ Main(Libc::Env &env) : env(env)
+ {
+ widget->show();
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env)
+{
+ Libc::with_libc([&] {
+
+ static Main main { env };
+
+ exit(main.app.exec());
+ });
+}
diff --git a/repos/gems/src/test/tiled_wm/panel/panel.cpp b/repos/gems/src/test/tiled_wm/panel/panel.cpp
new file mode 100644
index 0000000000..c7a2f8a074
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/panel.cpp
@@ -0,0 +1,144 @@
+/*
+ * \brief Tiled-WM test: panel widget
+ * \author Christian Helmuth
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Qt includes */
+#include
+#include
+
+/* local includes */
+#include "panel.h"
+
+
+void Panel_button::_clicked() { Q_EMIT clicked(text()); }
+void Panel_button::_toggled(bool checked) { Q_EMIT toggled(checked, text()); }
+
+
+Panel_button::Panel_button(QString label)
+{
+ if (!label.isNull()) {
+ setText(label);
+ }
+
+ setCheckable(true);
+ setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
+
+ _layout->addWidget(_icon, 0, Qt::AlignCenter);
+
+ connect(this, SIGNAL(clicked()), SLOT(_clicked()));
+ connect(this, SIGNAL(toggled(bool)), SLOT(_toggled(bool)));
+}
+
+
+Panel_button::~Panel_button() { }
+
+
+void App_bar::_app_button(QAbstractButton *b, bool checked)
+{
+ if (!checked) return;
+
+ if (Panel_button *button = qobject_cast(b)) {
+ Name name { button->text().toUtf8().constData() };
+
+ Genode::Reporter::Xml_generator xml(_content_request, [&] () {
+ xml.attribute("name", name);
+ });
+ }
+}
+
+
+void App_bar::_handle_apps()
+{
+ /* empty bar before adding current apps */
+ while (QLayoutItem *item = _layout->takeAt(0)) {
+ if (Panel_button *button = qobject_cast(item->widget())) {
+ _button_group->removeButton(button);
+ button->deleteLater();
+ }
+ delete item;
+ }
+
+ _apps.update();
+
+ Panel_button *visible_app_button = nullptr;
+
+ _apps.xml().for_each_sub_node("app", [&] (Genode::Xml_node node) {
+ QString const name { node.attribute_value("name", Name("no name")).string() };
+ bool const visible { node.attribute_value("visible", false) };
+ Panel_button *button { new Panel_button(name) };
+
+ if (visible) visible_app_button = button;
+
+ _button_group->addButton(button);
+ _layout->addWidget(button);
+ });
+
+ if (visible_app_button) visible_app_button->setChecked(true);
+}
+
+
+App_bar::App_bar(Genode::Env &env, Genode::Signal_receiver &sig_rec)
+:
+ _apps(env, "apps"), _content_request(env, "content_request"),
+ _apps_proxy(sig_rec)
+{
+ _content_request.enabled(true);
+
+ _button_group->setExclusive(true);
+
+ _handle_apps();
+
+ _apps.sigh(*_apps_proxy);
+
+ connect(_apps_proxy, SIGNAL(signal()), SLOT(_handle_apps()));
+ connect(_button_group, SIGNAL(buttonToggled(QAbstractButton *, bool)),
+ SLOT(_app_button(QAbstractButton *, bool)));
+}
+
+
+App_bar::~App_bar() { }
+
+
+void Panel::_wifi_toggled(bool checked)
+{
+ Genode::Reporter::Xml_generator xml(_overlay_request, [&] () {
+ xml.attribute("visible", checked);
+ });
+}
+
+
+Panel::Panel(Genode::Env &env, Genode::Signal_receiver &sig_rec)
+:
+ _overlay(env, "overlay"), _overlay_request(env, "overlay_request"),
+ _panel_button("Panel"), _app_bar(env, sig_rec)
+{
+ _layout->addWidget(_panel_button);
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_app_bar);
+ _layout->addWidget(new Spacer(), 1);
+ _layout->addWidget(_wifi_button);
+
+ _panel_button->setCheckable(false);
+ _panel_button->setToolTip("This panel is just an example.");
+
+ _wifi_button->setObjectName("wifi");
+ _wifi_button->setToolTip("Open WiFi overlay");
+
+ _overlay_request.enabled(true);
+
+ _wifi_toggled(_wifi_button->isChecked());
+
+ connect(_wifi_button, SIGNAL(toggled(bool)), SLOT(_wifi_toggled(bool)));
+}
+
+
+Panel::~Panel() { }
diff --git a/repos/gems/src/test/tiled_wm/panel/panel.h b/repos/gems/src/test/tiled_wm/panel/panel.h
new file mode 100644
index 0000000000..afe3eb70a6
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/panel.h
@@ -0,0 +1,109 @@
+/*
+ * \brief Tiled-WM test: panel widget
+ * \author Christian Helmuth
+ * \date 2018-09-27
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TEST__TILED_WM__PANEL__PANEL_H_
+#define _TEST__TILED_WM__PANEL__PANEL_H_
+
+/* Qt includes */
+#include
+#include
+#include
+#include
+
+/* Qoost includes */
+#include
+#include
+
+/* Genode includes */
+#include
+#include
+
+/* local includes */
+#include
+#include /* build-dir copy of qoost/icon.h works around missing "vtable for Icon" */
+
+
+class Panel_button : public Compound_widget
+{
+ Q_OBJECT
+
+ private:
+
+ QMember _icon;
+
+ private Q_SLOTS:
+
+ void _clicked();
+ void _toggled(bool);
+
+ public:
+
+ Panel_button(QString label = QString());
+ ~Panel_button();
+
+ Q_SIGNALS:
+
+ void clicked(QString label);
+ void toggled(bool checked, QString label);
+};
+
+
+class App_bar : public Compound_widget
+{
+ Q_OBJECT
+
+ private:
+
+ Genode::Attached_rom_dataspace _apps;
+ Genode::Reporter _content_request;
+
+ QMember _button_group;
+ QMember _apps_proxy;
+
+ private Q_SLOTS:
+
+ void _handle_apps();
+ void _app_button(QAbstractButton *, bool);
+
+ public:
+
+ App_bar(Genode::Env &, Genode::Signal_receiver &);
+ ~App_bar();
+};
+
+
+
+class Panel : public Compound_widget
+{
+ Q_OBJECT
+
+ private:
+
+ Genode::Attached_rom_dataspace _overlay;
+ Genode::Reporter _overlay_request;
+
+ QMember _panel_button;
+ QMember _app_bar;
+ QMember _wifi_button;
+
+ private Q_SLOTS:
+
+ void _wifi_toggled(bool);
+
+ public:
+
+ Panel(Genode::Env &, Genode::Signal_receiver &);
+ ~Panel();
+};
+
+#endif /* _TEST__TILED_WM__PANEL__PANEL_H_ */
diff --git a/repos/gems/src/test/tiled_wm/panel/panel.pro b/repos/gems/src/test/tiled_wm/panel/panel.pro
new file mode 100644
index 0000000000..3ec8f3f594
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/panel.pro
@@ -0,0 +1,6 @@
+TEMPLATE = app
+TARGET = test-tiled_wm-panel
+QT = core gui widgets
+SOURCES += main.cpp panel.cpp
+HEADERS += panel.h icon.h ../util.h
+RESOURCES = panel.qrc
diff --git a/repos/gems/src/test/tiled_wm/panel/panel.qrc b/repos/gems/src/test/tiled_wm/panel/panel.qrc
new file mode 100644
index 0000000000..9092a358d6
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/panel.qrc
@@ -0,0 +1,9 @@
+
+
+
+ ../style.qss
+ wifi.png
+
+
+
+
diff --git a/repos/gems/src/test/tiled_wm/panel/target.mk b/repos/gems/src/test/tiled_wm/panel/target.mk
new file mode 100644
index 0000000000..3b2def4254
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/panel/target.mk
@@ -0,0 +1,3 @@
+include $(PRG_DIR)/../target.inc
+
+panel.o main.o: icon.h
diff --git a/repos/gems/src/test/tiled_wm/panel/wifi.png b/repos/gems/src/test/tiled_wm/panel/wifi.png
new file mode 100644
index 0000000000..502591d38e
Binary files /dev/null and b/repos/gems/src/test/tiled_wm/panel/wifi.png differ
diff --git a/repos/gems/src/test/tiled_wm/style.qss b/repos/gems/src/test/tiled_wm/style.qss
new file mode 100644
index 0000000000..067e94aabd
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/style.qss
@@ -0,0 +1,22 @@
+Panel, Overlay, App { min-width: 100px; min-height: 100px; }
+
+QFrame { padding: 5px; }
+QLabel { color: white; }
+/*QPushButton { border: none; outline: none; }*/
+
+Panel {
+ background-color: rgb(51, 93, 128);
+}
+
+Panel_button Icon { qproperty-iconSize: 18px; }
+Panel_button#wifi Icon { qproperty-iconFile: url(:/wifi.png); }
+
+Overlay {
+ background-color: rgba(25, 69, 105, 100);
+}
+
+App {
+ background-color: rgb(96, 130, 159);
+}
+
+/* vi: set ft=css : */
diff --git a/repos/gems/src/test/tiled_wm/target.inc b/repos/gems/src/test/tiled_wm/target.inc
new file mode 100644
index 0000000000..723db9f456
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/target.inc
@@ -0,0 +1,25 @@
+include $(call select_from_repositories,src/app/qt5/tmpl/target_defaults.inc)
+
+include $(call select_from_repositories,src/app/qt5/tmpl/target_final.inc)
+
+CC_CXX_WARN_STRICT =
+
+INC_DIR += $(PRG_DIR)/..
+
+LIBS += qoost
+
+#
+# We need Qt headers in a local directory for MOC to work correctly
+#
+
+# icon.h from the qoost API package
+ICON_H = $(call select_from_repositories,include/qoost/icon.h)
+
+# fall-back to the qoost port directory if that fails
+ifeq ($(ICON_H),)
+ICON_H = $(call select_from_ports,qoost)/include/qoost/icon.h
+endif
+
+icon.h: $(ICON_H)
+ $(VERBOSE)cp $(ICON_H) .
+
diff --git a/repos/gems/src/test/tiled_wm/util.h b/repos/gems/src/test/tiled_wm/util.h
new file mode 100644
index 0000000000..6f0efee47a
--- /dev/null
+++ b/repos/gems/src/test/tiled_wm/util.h
@@ -0,0 +1,153 @@
+/*
+ * \brief Tiled-WM test: utilities
+ * \author Christian Helmuth
+ * \date 2018-09-26
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TEST__TILED_WM__UTIL_H_
+#define _TEST__TILED_WM__UTIL_H_
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+/* Qt includes */
+#include
+#include
+#include
+#include
+#include
+
+/* Qoost includes */
+#include
+
+/* Libc includes */
+#include
+
+
+typedef Genode::String<32> Name;
+
+
+/*
+ * Genode signal to queued Qt signal proxy
+ */
+class Genode_signal_proxy : public QObject,
+ public Genode::Signal_dispatcher
+{
+ Q_OBJECT
+
+ public:
+
+ Genode_signal_proxy(Genode::Signal_receiver &sig_rec)
+ :
+ Genode::Signal_dispatcher(
+ sig_rec, *this, &Genode_signal_proxy::handle_genode_signal)
+ {
+ connect(this, SIGNAL(internal_signal()),
+ this, SIGNAL(signal()),
+ Qt::QueuedConnection);
+ }
+
+ /* called by signal dispatcher / emits internal signal in context of
+ * signal-dispatcher thread */
+ void handle_genode_signal(unsigned = 0) { Q_EMIT internal_signal(); }
+
+ Q_SIGNALS:
+
+ /* internal_signal() is Qt::QueuedConnection to signal() */
+ void internal_signal();
+
+ /* finally signal() is emitted in the context of the Qt main thread */
+ void signal();
+};
+
+
+/*
+ * Genode signal dispatcher thread
+ */
+class Genode_signal_dispatcher : public Genode::Thread
+{
+ private:
+
+ Genode::Signal_receiver _sig_rec;
+
+ void entry()
+ {
+ /* dispatch signals */
+ while (true) {
+ Genode::Signal sig = _sig_rec.wait_for_signal();
+ Genode::Signal_dispatcher_base *dispatcher {
+ dynamic_cast(sig.context()) };
+
+ if (dispatcher)
+ dispatcher->dispatch(sig.num());
+ }
+ }
+
+ public:
+
+ Genode_signal_dispatcher(Genode::Env &env)
+ :
+ Genode::Thread(env, "signal_dispatcher", 0x4000)
+ {
+ start();
+ }
+
+ Genode::Signal_receiver &signal_receiver() { return _sig_rec; }
+};
+
+
+/*
+ * Qt initialization
+ */
+
+extern void initialize_qt_core(Genode::Env &);
+extern void initialize_qt_gui(Genode::Env &);
+
+static inline QApplication & qt5_initialization(Libc::Env &env)
+{
+ initialize_qt_core(env);
+ initialize_qt_gui(env);
+
+ char const *argv[] = { "qt5_app", 0 };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ static QApplication app(argc, (char**)argv);
+
+ QFile file(":style.qss");
+ if (!file.open(QFile::ReadOnly)) {
+ qWarning() << "Warning:" << file.errorString()
+ << "opening file" << file.fileName();
+ } else {
+ qApp->setStyleSheet(QLatin1String(file.readAll()));
+ }
+
+ app.connect(&app, SIGNAL(lastWindowClosed()), SLOT(quit()));
+
+ return app;
+}
+
+
+/*
+ * Widget utilities
+ */
+
+struct Spacer : QFrame
+{
+ Q_OBJECT public:
+
+ Spacer(QString const &style_id = "") { setObjectName(style_id); }
+
+ ~Spacer() { }
+};
+
+#endif /* _TEST__TILED_WM__UTIL_H_ */