Files
genode/repos/os/src/server/nitpicker/session_component.cc
Norman Feske ef741ef80d Change pixel format to 32 bits per pixel
Until now, Genode's framebuffer session interface was based on the
RGB565 pixel format. This patch changes the pixel format to 32-bit
XRGB where the X part is ignored. It adapts all graphical applications
and device drivers accordingly.

The patch also adjusts the users of the drivers_interactive packages,
assigning 64 MiB RAM and 1500 caps to the drivers subsystem, which is
sufficient for covering high resolutions at 32 bits per pixel and to
accommodate multi-component USB HID input stacks.

Fixes #3784
2020-06-29 14:22:29 +02:00

536 lines
13 KiB
C++

/*
* \brief Nitpicker session component
* \author Norman Feske
* \date 2017-11-16
*/
/*
* Copyright (C) 2006-2017 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 "view_stack.h"
using namespace Nitpicker;
void Session_component::_release_buffer()
{
if (!_texture)
return;
typedef Pixel_rgb888 PT;
Chunky_texture<PT> const *cdt = static_cast<Chunky_texture<PT> const *>(_texture);
_texture = nullptr;
_uses_alpha = false;
_input_mask = nullptr;
destroy(&_session_alloc, const_cast<Chunky_texture<PT> *>(cdt));
replenish(Ram_quota{_buffer_size});
_buffer_size = 0;
}
bool Session_component::_views_are_equal(View_handle v1, View_handle v2)
{
if (!v1.valid() || !v2.valid())
return false;
Weak_ptr<View_component> v1_ptr = _view_handle_registry.lookup(v1);
Weak_ptr<View_component> v2_ptr = _view_handle_registry.lookup(v2);
return v1_ptr == v2_ptr;
}
View_owner &Session_component::forwarded_focus()
{
Session_component *next_focus = this;
/* helper used for detecting cycles */
Session_component *next_focus_slow = next_focus;
for (bool odd = false; ; odd = !odd) {
/* we found the final focus once the forwarding stops */
if (!next_focus->_forwarded_focus)
break;
next_focus = next_focus->_forwarded_focus;
/* advance 'next_focus_slow' every odd iteration only */
if (odd)
next_focus_slow = next_focus_slow->_forwarded_focus;
/* a cycle is detected if 'next_focus' laps 'next_focus_slow' */
if (next_focus == next_focus_slow) {
error("cyclic focus forwarding by ", next_focus->label());
break;
}
}
return *next_focus;
}
void Session_component::_execute_command(Command const &command)
{
switch (command.opcode) {
case Command::OP_GEOMETRY:
{
Command::Geometry const &cmd = command.geometry;
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (!view.valid())
return;
Point pos = cmd.rect.p1();
/* transpose position of top-level views by vertical session offset */
if (view->top_level())
pos = _phys_pos(pos, _view_stack.size());
if (view.valid())
_view_stack.geometry(*view, Rect(pos, cmd.rect.area()));
return;
}
case Command::OP_OFFSET:
{
Command::Offset const &cmd = command.offset;
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (view.valid())
_view_stack.buffer_offset(*view, cmd.offset);
return;
}
case Command::OP_TO_FRONT:
{
Command::To_front const &cmd = command.to_front;
if (_views_are_equal(cmd.view, cmd.neighbor))
return;
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (!view.valid())
return;
/* bring to front if no neighbor is specified */
if (!cmd.neighbor.valid()) {
_view_stack.stack(*view, nullptr, true);
return;
}
/* stack view relative to neighbor */
Locked_ptr<View_component> neighbor(_view_handle_registry.lookup(cmd.neighbor));
if (neighbor.valid())
_view_stack.stack(*view, &(*neighbor), false);
return;
}
case Command::OP_TO_BACK:
{
Command::To_back const &cmd = command.to_back;
if (_views_are_equal(cmd.view, cmd.neighbor))
return;
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (!view.valid())
return;
/* bring to front if no neighbor is specified */
if (!cmd.neighbor.valid()) {
_view_stack.stack(*view, nullptr, false);
return;
}
/* stack view relative to neighbor */
Locked_ptr<View_component> neighbor(_view_handle_registry.lookup(cmd.neighbor));
if (neighbor.valid())
_view_stack.stack(*view, &(*neighbor), true);
return;
}
case Command::OP_BACKGROUND:
{
Command::Background const &cmd = command.background;
if (_provides_default_bg) {
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (!view.valid())
return;
view->background(true);
_view_stack.default_background(*view);
return;
}
/* revert old background view to normal mode */
if (_background)
_background->background(false);
/* assign session background */
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (!view.valid())
return;
_background = &(*view);
/* switch background view to background mode */
if (background())
view->background(true);
return;
}
case Command::OP_TITLE:
{
Command::Title const &cmd = command.title;
Locked_ptr<View_component> view(_view_handle_registry.lookup(cmd.view));
if (view.valid())
_view_stack.title(*view, _font, cmd.title.string());
return;
}
case Command::OP_NOP:
return;
}
}
void Session_component::_destroy_view(View_component &view)
{
if (_background == &view)
_background = nullptr;
/* reset background if view was used as default background */
if (_view_stack.is_default_background(view))
_view_stack.default_background(_builtin_background);
_view_stack.remove_view(view);
_env.ep().dissolve(view);
_view_list.remove(&view);
destroy(_view_alloc, &view);
}
void Session_component::destroy_all_views()
{
while (Session_view_list_elem *v = _view_list.first())
_destroy_view(*static_cast<View_component *>(v));
}
void Session_component::submit_input_event(Input::Event e)
{
using namespace Input;
Point const origin_offset = _phys_pos(Point(0, 0), _view_stack.size());
/*
* Transpose absolute coordinates by session-specific vertical offset.
*/
e.handle_absolute_motion([&] (int x, int y) {
e = Absolute_motion{max(0, x - origin_offset.x()),
max(0, y - origin_offset.y())}; });
e.handle_touch([&] (Touch_id id, float x, float y) {
e = Touch{ id, max(0.0f, x - origin_offset.x()),
max(0.0f, y - origin_offset.y())}; });
_input_session_component.submit(&e);
}
Session_component::View_handle Session_component::create_view(View_handle parent_handle)
{
View_component *view = nullptr;
/*
* Create child view
*/
if (parent_handle.valid()) {
try {
Locked_ptr<View_component> parent(_view_handle_registry.lookup(parent_handle));
if (!parent.valid())
return View_handle();
view = new (_view_alloc)
View_component(*this,
View_component::NOT_TRANSPARENT, View_component::NOT_BACKGROUND,
&(*parent));
parent->add_child(*view);
}
catch (View_handle_registry::Lookup_failed) { return View_handle(); }
catch (View_handle_registry::Out_of_memory) { throw Out_of_ram(); }
}
/*
* Create top-level view
*/
else {
try {
view = new (_view_alloc)
View_component(*this,
View_component::NOT_TRANSPARENT, View_component::NOT_BACKGROUND,
nullptr);
}
catch (Allocator::Out_of_memory) { throw Out_of_ram(); }
}
view->title(_font, "");
view->apply_origin_policy(_pointer_origin);
_view_list.insert(view);
_env.ep().manage(*view);
try {
return _view_handle_registry.alloc(*view);
}
catch (View_handle_registry::Out_of_memory) { throw Out_of_ram(); }
}
void Session_component::apply_session_policy(Xml_node config,
Domain_registry const &domain_registry)
{
reset_domain();
try {
Session_policy policy(_label, config);
/* read domain attribute */
if (!policy.has_attribute("domain")) {
error("policy for label \"", _label, "\" lacks domain declaration");
return;
}
typedef Domain_registry::Entry::Name Name;
Name const name = policy.attribute_value("domain", Name());
_domain = domain_registry.lookup(name);
if (!_domain)
error("policy for label \"", _label,
"\" specifies nonexistent domain \"", name, "\"");
} catch (...) {
error("no policy matching label \"", _label, "\""); }
}
void Session_component::destroy_view(View_handle handle)
{
/*
* Search view object given the handle
*
* We cannot look up the view directly from the
* '_view_handle_registry' because we would obtain a weak
* pointer to the view object. If we called the object's
* destructor from the corresponding locked pointer, the
* call of 'lock_for_destruction' in the view's destructor
* would attempt to take the lock again.
*/
for (Session_view_list_elem *v = _view_list.first(); v; v = v->next()) {
auto handle_matches = [&] (View_component const &view)
{
try { return _view_handle_registry.has_handle(view, handle); }
/* 'Handle_registry::has_handle' may throw */
catch (...) { return false; };
};
View_component &view = *static_cast<View_component *>(v);
if (handle_matches(view)) {
_destroy_view(view);
_view_handle_registry.free(handle);
break;
}
}
}
Session_component::View_handle
Session_component::view_handle(View_capability view_cap, View_handle handle)
{
auto lambda = [&] (View_component *view)
{
return (view) ? _view_handle_registry.alloc(*view, handle)
: View_handle();
};
try {
return _env.ep().rpc_ep().apply(view_cap, lambda);
}
catch (View_handle_registry::Out_of_memory) { throw Out_of_ram(); }
}
View_capability Session_component::view_capability(View_handle handle)
{
try {
Locked_ptr<View_component> view(_view_handle_registry.lookup(handle));
return view.valid() ? view->cap() : View_capability();
}
catch (View_handle_registry::Lookup_failed) { return View_capability(); }
}
void Session_component::release_view_handle(View_handle handle)
{
try {
_view_handle_registry.free(handle); }
catch (View_handle_registry::Lookup_failed) {
warning("view lookup failed while releasing view handle");
return;
}
}
void Session_component::execute()
{
for (unsigned i = 0; i < _command_buffer.num(); i++) {
try {
_execute_command(_command_buffer.get(i)); }
catch (View_handle_registry::Lookup_failed) {
warning("view lookup failed during command execution"); }
}
}
Framebuffer::Mode Session_component::mode()
{
return Framebuffer::Mode { .area = screen_area(_framebuffer.mode().area) };
}
void Session_component::buffer(Framebuffer::Mode mode, bool use_alpha)
{
/* check if the session quota suffices for the specified mode */
if (_buffer_size + _ram_quota_guard().avail().value < ram_quota(mode, use_alpha))
throw Out_of_ram();
/* buffer re-allocation may consume new dataspace capability if buffer is new */
if (_cap_quota_guard().avail().value < 1)
throw Out_of_caps();
_framebuffer_session_component.notify_mode_change(mode, use_alpha);
}
void Session_component::focus(Capability<Gui::Session> session_cap)
{
if (this->cap() == session_cap)
return;
_forwarded_focus = nullptr;
_env.ep().rpc_ep().apply(session_cap, [&] (Session_component *session) {
if (session)
_forwarded_focus = session; });
_focus_updater.update_focus();
}
void Session_component::session_control(Label suffix, Session_control control)
{
switch (control) {
case SESSION_CONTROL_HIDE:
_visibility_controller.hide_matching_sessions(label(), suffix);
break;
case SESSION_CONTROL_SHOW:
_visibility_controller.show_matching_sessions(label(), suffix);
break;
case SESSION_CONTROL_TO_FRONT:
_view_stack.to_front(Label(label(), suffix).string());
break;
}
}
Buffer *Session_component::realloc_buffer(Framebuffer::Mode mode, bool use_alpha)
{
typedef Pixel_rgb888 PT;
_buffer_size = Chunky_texture<PT>::calc_num_bytes(mode.area, use_alpha);
/*
* Preserve the content of the original buffer if nitpicker has
* enough slack memory to temporarily keep the original pixels.
*/
Texture<PT> const *src_texture = nullptr;
if (texture()) {
enum { PRESERVED_RAM = 128*1024 };
if (_env.pd().avail_ram().value > _buffer_size + PRESERVED_RAM) {
src_texture = static_cast<Texture<PT> const *>(texture());
} else {
warning("not enough RAM to preserve buffer content during resize");
_release_buffer();
}
}
Ram_quota const temporary_ram_upgrade = src_texture
? Ram_quota{_buffer_size} : Ram_quota{0};
_ram_quota_guard().upgrade(temporary_ram_upgrade);
auto try_alloc_texture = [&] ()
{
try {
return new (&_session_alloc)
Chunky_texture<PT>(_env.ram(), _env.rm(), mode.area, use_alpha);
} catch (...) {
return (Chunky_texture<PT>*)nullptr;
}
};
Chunky_texture<PT> * const texture = try_alloc_texture();
/* copy old buffer content into new buffer and release old buffer */
if (src_texture) {
Surface<PT> surface(texture->pixel(),
texture->Texture_base::size());
Texture_painter::paint(surface, *src_texture, Color(), Point(0, 0),
Texture_painter::SOLID, false);
_release_buffer();
if (!_ram_quota_guard().try_downgrade(temporary_ram_upgrade))
warning("accounting error during framebuffer realloc");
}
try { withdraw(Ram_quota{_buffer_size}); }
catch (...) {
destroy(&_session_alloc, texture);
_buffer_size = 0;
return nullptr;
}
_texture = texture;
_uses_alpha = use_alpha;
_input_mask = texture->input_mask_buffer();
return texture;
}