diff --git a/repos/os/include/capture_session/capture_session.h b/repos/os/include/capture_session/capture_session.h
new file mode 100644
index 0000000000..6e17db7976
--- /dev/null
+++ b/repos/os/include/capture_session/capture_session.h
@@ -0,0 +1,134 @@
+/*
+ * \brief Capture session interface
+ * \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.
+ */
+
+#ifndef _INCLUDE__CAPTURE_SESSION__CAPTURE_SESSION_H_
+#define _INCLUDE__CAPTURE_SESSION__CAPTURE_SESSION_H_
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Capture {
+
+ using namespace Genode;
+
+ struct Session_client;
+ struct Session;
+
+ using Rect = Surface_base::Rect;
+ using Point = Surface_base::Point;
+ using Area = Surface_base::Area;
+ using Pixel = Pixel_rgb888;
+
+}
+
+
+struct Capture::Session : Genode::Session
+{
+ /**
+ * \noapi
+ */
+ static const char *service_name() { return "Capture"; }
+
+ /*
+ * A capture session consumes a dataspace capability for the server's
+ * session-object allocation, a session capability, and a dataspace
+ * capability for the pixel buffer.
+ */
+ enum { CAP_QUOTA = 3 };
+
+ /**
+ * Return number of bytes needed for pixel buffer of specified size
+ */
+ static size_t buffer_bytes(Area size)
+ {
+ size_t const bytes_per_pixel = 4;
+ return bytes_per_pixel*size.count();
+ }
+
+ /**
+ * Request current screen size
+ */
+ virtual Area screen_size() const = 0;
+
+ /**
+ * Register signal handler to be notified whenever the screen size changes
+ */
+ virtual void screen_size_sigh(Signal_context_capability) = 0;
+
+ /**
+ * Define dimensions of the shared pixel buffer
+ *
+ * The 'size' controls the server-side allocation of the shared pixel
+ * buffer and may affect the screen size of the GUI server. The screen
+ * size is bounding box of the pixel buffers of all capture clients.
+ *
+ * \throw Out_of_ram session quota does not suffice for specified
+ * buffer dimensions
+ * \throw Out_of_caps
+ */
+ virtual void buffer(Area size) = 0;
+
+ /**
+ * Request dataspace of the shared pixel buffer defined via 'buffer'
+ */
+ virtual Dataspace_capability dataspace() = 0;
+
+ /**
+ * Result type of 'capture_at'
+ *
+ * The geometry information are relative to the viewport specified for the
+ * 'capture_at' call.
+ */
+ struct Affected_rects
+ {
+ enum { NUM_RECTS = 3U };
+
+ Rect rects[NUM_RECTS];
+
+ template
+ void for_each_rect(FN const &fn) const
+ {
+ for (unsigned i = 0; i < NUM_RECTS; i++)
+ if (rects[i].valid())
+ fn(rects[i]);
+ }
+ };
+
+ /**
+ * Update the pixel-buffer with content at the specified screen position
+ *
+ * \return geometry information about the content that changed since the
+ * previous call of 'capture_at'
+ */
+ virtual Affected_rects capture_at(Point) = 0;
+
+
+ /*********************
+ ** RPC declaration **
+ *********************/
+
+ GENODE_RPC(Rpc_screen_size, Area, screen_size);
+ GENODE_RPC(Rpc_screen_size_sigh, void, screen_size_sigh, Signal_context_capability);
+ GENODE_RPC_THROW(Rpc_buffer, void, buffer,
+ GENODE_TYPE_LIST(Out_of_ram, Out_of_caps), Area);
+ GENODE_RPC(Rpc_dataspace, Dataspace_capability, dataspace);
+ GENODE_RPC(Rpc_capture_at, Affected_rects, capture_at, Point);
+
+ GENODE_RPC_INTERFACE(Rpc_screen_size, Rpc_screen_size_sigh, Rpc_buffer,
+ Rpc_dataspace, Rpc_capture_at);
+};
+
+#endif /* _INCLUDE__CAPTURE_SESSION__CAPTURE_SESSION_H_ */
diff --git a/repos/os/include/capture_session/client.h b/repos/os/include/capture_session/client.h
new file mode 100644
index 0000000000..bf032d8d9f
--- /dev/null
+++ b/repos/os/include/capture_session/client.h
@@ -0,0 +1,47 @@
+/*
+ * \brief Client-side capture session interface
+ * \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.
+ */
+
+#ifndef _INCLUDE__CAPTURE_SESSION__CLIENT_H_
+#define _INCLUDE__CAPTURE_SESSION__CLIENT_H_
+
+#include
+#include
+
+namespace Capture { struct Session_client; }
+
+
+struct Capture::Session_client : public Genode::Rpc_client
+{
+ /**
+ * Constructor
+ */
+ Session_client(Capability session) : Rpc_client(session) { }
+
+ Area screen_size() const override { return call(); }
+
+ void screen_size_sigh(Signal_context_capability sigh) override
+ {
+ call(sigh);
+ }
+
+ void buffer(Area size) override { call(size); }
+
+ Dataspace_capability dataspace() override { return call(); }
+
+ Affected_rects capture_at(Point pos) override
+ {
+ return call(pos);
+ }
+};
+
+#endif /* _INCLUDE__CAPTURE_SESSION__CLIENT_H_ */
diff --git a/repos/os/include/capture_session/connection.h b/repos/os/include/capture_session/connection.h
new file mode 100644
index 0000000000..5123702cdb
--- /dev/null
+++ b/repos/os/include/capture_session/connection.h
@@ -0,0 +1,114 @@
+/*
+ * \brief Connection to capture service
+ * \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.
+ */
+
+#ifndef _INCLUDE__CAPTURE_SESSION__CONNECTION_H_
+#define _INCLUDE__CAPTURE_SESSION__CONNECTION_H_
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Capture { class Connection; }
+
+
+class Capture::Connection : public Genode::Connection,
+ public Session_client
+{
+ public:
+
+ enum { RAM_QUOTA = 36*1024UL };
+
+ private:
+
+ size_t _session_quota = 0;
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Connection(Genode::Env &env, char const *label = "")
+ :
+ Genode::Connection(
+ env, session(env.parent(),
+ "ram_quota=%u, cap_quota=%u, label=\"%s\"",
+ RAM_QUOTA, CAP_QUOTA, label)),
+ Session_client(cap())
+ { }
+
+ void buffer(Area size) override
+ {
+ size_t const needed = buffer_bytes(size);
+ size_t const upgrade = needed > _session_quota
+ ? needed - _session_quota
+ : 0;
+ if (upgrade > 0) {
+ this->upgrade_ram(upgrade);
+ _session_quota += upgrade;
+ }
+
+ Session_client::buffer(size);
+ }
+
+ struct Screen;
+};
+
+
+class Capture::Connection::Screen
+{
+ public:
+
+ Area const size;
+
+ private:
+
+ Capture::Connection &_connection;
+
+ bool const _buffer_initialized = ( _connection.buffer(size), true );
+
+ Attached_dataspace _ds;
+
+ Texture const _texture { _ds.local_addr(), nullptr, size };
+
+ public:
+
+ Screen(Capture::Connection &connection, Region_map &rm, Area size)
+ :
+ size(size), _connection(connection), _ds(rm, _connection.dataspace())
+ { }
+
+ template
+ void with_texture(FN const &fn) const
+ {
+ fn(_texture);
+ }
+
+ void apply_to_surface(Surface &surface)
+ {
+ Affected_rects const affected = _connection.capture_at(Capture::Point(0, 0));
+
+ with_texture([&] (Texture const &texture) {
+
+ affected.for_each_rect([&] (Capture::Rect const rect) {
+
+ surface.clip(rect);
+
+ Blit_painter::paint(surface, texture, Capture::Point(0, 0));
+ });
+ });
+ }
+};
+
+#endif /* _INCLUDE__CAPTURE_SESSION__CONNECTION_H_ */
diff --git a/repos/os/recipes/api/capture_session/content.mk b/repos/os/recipes/api/capture_session/content.mk
new file mode 100644
index 0000000000..8852654dfa
--- /dev/null
+++ b/repos/os/recipes/api/capture_session/content.mk
@@ -0,0 +1,2 @@
+MIRRORED_FROM_REP_DIR := include/capture_session
+include $(REP_DIR)/recipes/api/session.inc
diff --git a/repos/os/recipes/api/capture_session/hash b/repos/os/recipes/api/capture_session/hash
new file mode 100644
index 0000000000..39062a819a
--- /dev/null
+++ b/repos/os/recipes/api/capture_session/hash
@@ -0,0 +1 @@
+2020-07-01 0d69d596b5fa9020478a318eb6751f5f25aebedb