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)