Flif_capture screenshot tool

A tool for capturing screenshots from a Framebuffer session.
This commit is contained in:
Emery Hemingway
2019-01-23 17:19:20 +01:00
committed by Norman Feske
parent 6628d03126
commit 71afee4799
11 changed files with 436 additions and 1 deletions

View File

@@ -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'.

View File

@@ -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 <flif_enc.h>
/* Libc includes */
#include <time.h>
/* Genode includes */
#include <libc/component.h>
#include <framebuffer_session/connection.h>
#include <input_session/connection.h>
#include <input/component.h>
#include <base/attached_dataspace.h>
#include <os/static_root.h>
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<uint16_t const>();
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<Framebuffer::Session>
{
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<Main> _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<Framebuffer::Session> _fb_root {
_env.ep().manage(_fb_session) };
Genode::Static_root<Input::Session> _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); }

View File

@@ -0,0 +1,4 @@
TARGET = flif_capture
SRC_CC = main.cc
LIBS = libflif libc blit base
INC_DIR += $(PRG_DIR)