diff --git a/repos/os/src/test/capture/main.cc b/repos/os/src/test/capture/main.cc
new file mode 100644
index 0000000000..34317bd0ea
--- /dev/null
+++ b/repos/os/src/test/capture/main.cc
@@ -0,0 +1,236 @@
+/*
+ * \brief Capture test
+ * \author Norman Feske
+ * \date 2020-06-26
+ */
+
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Test {
+
+ using namespace Genode;
+
+ struct View;
+ struct Main;
+}
+
+
+class Test::View
+{
+ private:
+
+ using View_handle = Gui::Session::View_handle;
+
+ Gui::Session_client &_gui;
+ View_handle const _handle = _gui.create_view(View_handle{});
+ Gui::Rect const _rect;
+
+ public:
+
+ View(Gui::Session_client &gui, Gui::Rect rect) : _gui(gui), _rect(rect)
+ {
+ using Command = Gui::Session::Command;
+
+ _gui.enqueue(_handle, rect);
+ _gui.enqueue(_handle, View_handle());
+ _gui.execute();
+ }
+
+ virtual ~View() { }
+};
+
+
+struct Test::Main
+{
+ Env &_env;
+
+ using Pixel = Capture::Pixel;
+ using Affected_rects = Capture::Session::Affected_rects;
+
+ Attached_rom_dataspace _config { _env, "config" };
+
+ Heap _heap { _env.ram(), _env.rm() };
+
+ static Gui::Point _point_from_xml(Xml_node node)
+ {
+ return Gui::Point(node.attribute_value("xpos", 0L),
+ node.attribute_value("ypos", 0L));
+ }
+
+ static Gui::Area _area_from_xml(Xml_node node, Gui::Area default_area)
+ {
+ return Gui::Area(node.attribute_value("width", default_area.w()),
+ node.attribute_value("height", default_area.h()));
+ }
+
+ struct Output
+ {
+ struct Invalid_config : Exception { };
+
+ Env &_env;
+
+ Allocator &_alloc;
+
+ Gui::Connection _gui { _env, "" };
+
+ Framebuffer::Mode const _mode;
+
+ void _validate_mode() const
+ {
+ if (_mode.area.count() == 0) {
+ error("invalid or missing 'width' and 'height' config attributes");
+ throw Invalid_config();
+ }
+ }
+
+ bool _gui_buffer_init = ( _validate_mode(), _gui.buffer(_mode, false), true );
+
+ Attached_dataspace _fb_ds { _env.rm(), _gui.framebuffer()->dataspace() };
+
+ Registry> _views { };
+
+ Output(Env &env, Allocator &alloc, Xml_node const &config)
+ :
+ _env(env), _alloc(alloc),
+ _mode({ .area = _area_from_xml(config, Area { }) })
+ {
+ auto view_rect = [&] (Xml_node node)
+ {
+ return Gui::Rect(_point_from_xml(node),
+ _area_from_xml(node, _mode.area));
+ };
+
+ config.for_each_sub_node("view", [&] (Xml_node node) {
+ new (_alloc)
+ Registered(_views, _gui, view_rect(node)); });
+ }
+
+ ~Output()
+ {
+ _views.for_each([&] (Registered &view) {
+ destroy(_alloc, &view); });
+ }
+
+ template
+ void with_surface(FN const &fn)
+ {
+ Surface surface(_fb_ds.local_addr(), _mode.area);
+
+ fn(surface);
+ }
+ };
+
+ Constructible