mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-16 01:39:59 +00:00
afcad2a968
This commit changes the 'Input::Event' type to be more safe and to deliver symbolic character information along with press events. Issue #2761 Fixes #2786
518 lines
12 KiB
C++
518 lines
12 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_rgb565 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));
|
|
|
|
_session_alloc.upgrade(_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 Domain_name;
|
|
char buf[sizeof(Domain_name)];
|
|
buf[0] = 0;
|
|
try {
|
|
policy.attribute("domain").value(buf, sizeof(buf)); }
|
|
catch (...) { }
|
|
|
|
Domain_name name(buf);
|
|
_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()) {
|
|
|
|
try {
|
|
View_component &view = *static_cast<View_component *>(v);
|
|
if (_view_handle_registry.has_handle(view, handle)) {
|
|
_destroy_view(view);
|
|
break;
|
|
}
|
|
} catch (View_handle_registry::Lookup_failed) { }
|
|
}
|
|
|
|
_view_handle_registry.free(handle);
|
|
}
|
|
|
|
|
|
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()
|
|
{
|
|
Area const phys_area(_framebuffer.mode().width(),
|
|
_framebuffer.mode().height());
|
|
|
|
Area const session_area = screen_area(phys_area);
|
|
|
|
return Framebuffer::Mode(session_area.w(), session_area.h(),
|
|
_framebuffer.mode().format());
|
|
}
|
|
|
|
|
|
void Session_component::buffer(Framebuffer::Mode mode, bool use_alpha)
|
|
{
|
|
/* check if the session quota suffices for the specified mode */
|
|
if (_session_alloc.quota() < ram_quota(mode, use_alpha))
|
|
throw Out_of_ram();
|
|
|
|
_framebuffer_session_component.notify_mode_change(mode, use_alpha);
|
|
}
|
|
|
|
|
|
void Session_component::focus(Capability<Nitpicker::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_rgb565 PT;
|
|
|
|
Area const size(mode.width(), mode.height());
|
|
|
|
_buffer_size = Chunky_texture<PT>::calc_num_bytes(size, use_alpha);
|
|
|
|
/*
|
|
* Preserve the content of the original buffer if nitpicker has
|
|
* enough lack memory to temporarily keep the original pixels.
|
|
*/
|
|
Texture<PT> const *src_texture = nullptr;
|
|
if (texture()) {
|
|
|
|
enum { PRESERVED_RAM = 128*1024 };
|
|
if (_env.ram().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();
|
|
}
|
|
}
|
|
|
|
Chunky_texture<PT> * const texture = new (&_session_alloc)
|
|
Chunky_texture<PT>(_env.ram(), _env.rm(), size, use_alpha);
|
|
|
|
/* 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 (!_session_alloc.withdraw(_buffer_size)) {
|
|
destroy(&_session_alloc, texture);
|
|
_buffer_size = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
_texture = texture;
|
|
_uses_alpha = use_alpha;
|
|
_input_mask = texture->input_mask_buffer();
|
|
|
|
return texture;
|
|
}
|