diff --git a/recipes/pkg/flif_capture/README b/recipes/pkg/flif_capture/README
new file mode 100644
index 0000000..a009753
--- /dev/null
+++ b/recipes/pkg/flif_capture/README
@@ -0,0 +1,2 @@
+
+ FLIF screen capture component
diff --git a/recipes/pkg/flif_capture/archives b/recipes/pkg/flif_capture/archives
new file mode 100644
index 0000000..9894e3e
--- /dev/null
+++ b/recipes/pkg/flif_capture/archives
@@ -0,0 +1,10 @@
+_/src/flif_capture
+_/src/init
+_/src/libc
+_/src/libflif
+_/src/libpng
+_/src/nit_fb
+_/src/nitpicker
+_/src/stdcxx
+_/src/vfs
+_/src/zlib
diff --git a/recipes/pkg/flif_capture/hash b/recipes/pkg/flif_capture/hash
new file mode 100644
index 0000000..39cdd0d
--- /dev/null
+++ b/recipes/pkg/flif_capture/hash
@@ -0,0 +1 @@
+-
diff --git a/recipes/pkg/flif_capture/runtime b/recipes/pkg/flif_capture/runtime
new file mode 100644
index 0000000..86b1be6
--- /dev/null
+++ b/recipes/pkg/flif_capture/runtime
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/recipes/pkg/flif_view/hash b/recipes/pkg/flif_view/hash
index f9b3c12..39cdd0d 100644
--- a/recipes/pkg/flif_view/hash
+++ b/recipes/pkg/flif_view/hash
@@ -1 +1 @@
-18.05 4a21e700a1b61ba1945b286edc7c4355a014aa26
+-
diff --git a/recipes/src/flif_capture/content.mk b/recipes/src/flif_capture/content.mk
new file mode 100644
index 0000000..9eb63ef
--- /dev/null
+++ b/recipes/src/flif_capture/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR := src/server/flif_capture
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/recipes/src/flif_capture/hash b/recipes/src/flif_capture/hash
new file mode 100644
index 0000000..39cdd0d
--- /dev/null
+++ b/recipes/src/flif_capture/hash
@@ -0,0 +1 @@
+-
diff --git a/recipes/src/flif_capture/used_apis b/recipes/src/flif_capture/used_apis
new file mode 100644
index 0000000..864bb57
--- /dev/null
+++ b/recipes/src/flif_capture/used_apis
@@ -0,0 +1,11 @@
+base
+os
+blit
+framebuffer_session
+input_session
+libc
+libflif
+libpng
+stdcxx
+vfs
+zlib
diff --git a/src/server/flif_capture/README b/src/server/flif_capture/README
new file mode 100644
index 0000000..8fb1115
--- /dev/null
+++ b/src/server/flif_capture/README
@@ -0,0 +1,14 @@
+The 'flif_capture' server captures screenshots of a framebuffer session by
+proxying a 'Framebuffer' and an 'Input' session. Screenshots are triggered
+when the "Print Screen" ("PrtSc" on Thinkpads) key event is intercepted
+and are written to a VFS instance in the FLIF image format.
+
+Usage
+-----
+
+Only few Genode components make direct 'Input' and 'Framebuffer' connections,
+the recommendation is to use these session bundled in a 'Nitpicker' connection.
+Therefore to screenshot 'Nitpicker' clients a Nitpicker server must be running
+as a client of the 'flif_capture' server, and a window manager may be necessary
+as well. The 'flif_capture', 'nitpicker', and 'wm' stack may be hosted recursively
+by using the 'nit_fb' server beneath 'flif_capture'.
diff --git a/src/server/flif_capture/main.cc b/src/server/flif_capture/main.cc
new file mode 100644
index 0000000..2e8c3fd
--- /dev/null
+++ b/src/server/flif_capture/main.cc
@@ -0,0 +1,301 @@
+/*
+ * \brief Screen capture utility
+ * \author Emery Hemingway
+ * \date 2019-01-22
+ */
+
+/*
+ * Copyright (C) 2019 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.
+ */
+
+/* FLIF includes */
+#include
+
+/* Libc includes */
+#include
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Flif_capture {
+ using namespace Genode;
+
+ class Framebuffer_session_component;
+ class Encoder;
+ class Main;
+
+ using Framebuffer::Mode;
+
+ /**
+ * Endian work-around struct
+ */
+ struct RGBA
+ {
+ uint8_t r,g,b,a;
+ } __attribute__((packed));
+}
+
+
+class Flif_capture::Encoder : Genode::Thread
+{
+ private:
+
+ Genode::Env &_env;
+ FLIF_IMAGE *_image = nullptr;
+ Semaphore _semaphore { };
+ Lock _lock { Lock::LOCKED };
+
+ static size_t stack_size()
+ {
+ auto info = Thread::mystack();
+ return info.top - info.base;
+ }
+
+ protected:
+
+ void entry()
+ {
+ for (;;) {
+ while (_image == nullptr) {
+ _lock.unlock();
+ _semaphore.down();
+ _lock.lock();
+ }
+
+ char filename[32] { '\0' };
+
+ Libc::with_libc([&] () {
+ /* calculate Sumerian time, hopefully */
+ time_t now = time(NULL);
+ struct tm more_now { };
+ localtime_r(&now, &more_now);
+ strftime(filename, sizeof(filename), "%T.flif", &more_now);
+ });
+
+ FLIF_ENCODER* encoder = flif_create_encoder();
+ if (!encoder) {
+ Genode::error("failed to create FLIF encoder");
+ continue;
+ }
+
+ flif_encoder_add_image(encoder, _image);
+
+ Genode::log("capture to ", (char const *)filename);
+ Libc::with_libc([&] () {
+ if (!flif_encoder_encode_file(encoder, filename))
+ Genode::error("file encoding failed");
+ });
+
+ flif_destroy_encoder(encoder);
+ flif_destroy_image(_image);
+ _image = nullptr;
+ }
+ }
+
+ public:
+
+ bool capture_pending = false;
+
+ /* safety noise */
+ Encoder(Encoder const &);
+ Encoder &operator = (Encoder const &);
+
+ Encoder(Genode::Env &env)
+ :
+ Thread(env, Thread::Name("encoder"), stack_size(),
+ env.cpu().affinity_space().location_of_index(1),
+ (Thread::Weight)Thread::Weight::DEFAULT_WEIGHT,
+ env.cpu()),
+ _env(env)
+ {
+ start();
+ }
+
+ void queue(Dataspace_capability fb_cap, Mode const mode)
+ {
+ Lock::Guard guard(_lock);
+
+ if (_image != nullptr)
+ return;
+
+ /* TODO: attach/detach each capture? */
+ Genode::Attached_dataspace fb_ds(_env.rm(), fb_cap);
+ if ((mode.format() != Mode::RGB565)
+ || (fb_ds.size() < (mode.width() * mode.height() * mode.bytes_per_pixel()))) {
+ Genode::error("invalid framebuffer for capture");
+ return;
+ }
+
+ RGBA row[mode.width()];
+
+ _image = flif_create_image(mode.width(), mode.height());
+ if (!_image) {
+ Genode::error("failed to create image buffer");
+ return;
+ }
+
+ uint16_t const *pixels = fb_ds.local_addr();
+
+ for (int y = 0; y < mode.height(); y++) {
+ for (int x = 0; x < mode.width(); x++) {
+ uint16_t px = pixels[y*mode.width()+x];
+ row[x] = RGBA {
+ (uint8_t)((px & 0xf800) >> 8),
+ (uint8_t)((px & 0x07e0) >> 3),
+ (uint8_t)((px & 0x1f) << 3),
+ (uint8_t)(0xff)
+ };
+ }
+ flif_image_write_row_RGBA8(_image, y, row, sizeof(row));
+ }
+
+ _semaphore.up();
+ }
+};
+
+
+class Flif_capture::Framebuffer_session_component
+:
+ public Genode::Rpc_object
+{
+ private:
+
+ Genode::Env &_env;
+ Framebuffer::Session &_parent;
+ Flif_capture::Encoder &_encoder;
+
+ Genode::Dataspace_capability _dataspace { };
+ Framebuffer::Mode _mode { };
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Framebuffer_session_component(Genode::Env &env,
+ Framebuffer::Session &client,
+ Flif_capture::Encoder &encoder)
+ : _env(env), _parent(client), _encoder(encoder) { }
+
+
+ /************************************
+ ** Framebuffer::Session interface **
+ ************************************/
+
+ Genode::Dataspace_capability dataspace() override
+ {
+ _mode = _parent.mode();
+ _dataspace = _parent.dataspace();
+ return _dataspace;
+ }
+
+ Mode mode() const override
+ {
+ return _parent.mode();
+ }
+
+ void mode_sigh(Genode::Signal_context_capability sigh) override
+ {
+ _parent.mode_sigh(sigh);
+ }
+
+ void refresh(int x, int y, int w, int h) override
+ {
+ _parent.refresh(x, y, w, h);
+ if (_encoder.capture_pending) {
+ if (_dataspace.valid()) {
+ _encoder.queue(_dataspace, _mode);
+ _encoder.capture_pending = false;
+ }
+ }
+ }
+
+ void sync_sigh(Genode::Signal_context_capability sigh) override
+ {
+ _parent.sync_sigh(sigh);
+ }
+};
+
+
+class Flif_capture::Main
+{
+ private:
+
+ Genode::Env &_env;
+
+ Flif_capture::Encoder _encoder { _env };
+ Framebuffer::Connection _parent_fb { _env, Mode(0, 0, Mode::RGB565) };
+ Input::Connection _parent_input { _env };
+
+ Framebuffer_session_component _fb_session {
+ _env, _parent_fb, _encoder };
+
+ Input::Session_component _input_session {
+ _env, _env.ram() };
+
+ Framebuffer::Session_capability _fb_cap { _env.ep().manage(_fb_session) };
+ Input::Session_capability _input_cap { _env.ep().manage(_input_session) };
+
+ Genode::Signal_handler _input_handler {
+ _env.ep(), *this, &Main::_handle_input };
+
+ Input::Keycode _capture_code = Input::KEY_PRINT;
+
+ void _handle_input()
+ {
+ using namespace Input;
+
+ Event_queue &queue = _input_session.event_queue();
+
+ _parent_input.for_each_event([&] (Event const &e) {
+ enum { SUBMIT_LATER = false };
+ queue.add(e, SUBMIT_LATER);
+
+ if (e.key_release(_capture_code)) {
+ _encoder.capture_pending = true;
+ }
+ });
+
+ queue.submit_signal();
+ }
+
+ Genode::Static_root _fb_root {
+ _env.ep().manage(_fb_session) };
+
+ Genode::Static_root _input_root {
+ _env.ep().manage(_input_session) };
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Main(Libc::Env &env) : _env(env)
+ {
+ /* register input handler */
+ _parent_input.sigh(_input_handler);
+
+ _input_session.event_queue().enabled(true);
+
+ env.parent().announce(env.ep().manage(_fb_root));
+ env.parent().announce(env.ep().manage(_input_root));
+
+ Genode::log("--- screenshot capture key is ", Input::key_name(_capture_code), " ---");
+ }
+};
+
+
+/***************
+ ** Component **
+ ***************/
+
+void Libc::Component::construct(Libc::Env &env) {
+ static Flif_capture::Main inst(env); }
diff --git a/src/server/flif_capture/target.mk b/src/server/flif_capture/target.mk
new file mode 100644
index 0000000..2a791fe
--- /dev/null
+++ b/src/server/flif_capture/target.mk
@@ -0,0 +1,4 @@
+TARGET = flif_capture
+SRC_CC = main.cc
+LIBS = libflif libc blit base
+INC_DIR += $(PRG_DIR)