genode/repos/gems/src/server/wm/decorator_nitpicker.h

497 lines
12 KiB
C++

/*
* \brief Nitpicker service provided to decorator
* \author Norman Feske
* \date 2014-02-14
*/
/*
* Copyright (C) 2014-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.
*/
#ifndef _DECORATOR_NITPICKER_H_
#define _DECORATOR_NITPICKER_H_
/* Genode includes */
#include <util/string.h>
#include <base/attached_dataspace.h>
#include <os/reporter.h>
#include <nitpicker_session/connection.h>
#include <input_session/client.h>
#include <input/event.h>
#include <input/component.h>
/* local includes */
#include <window_registry.h>
#include <last_motion.h>
namespace Wm { class Main;
using Genode::size_t;
using Genode::Allocator;
using Genode::Arg_string;
using Genode::Object_pool;
using Genode::Attached_dataspace;
using Genode::Attached_ram_dataspace;
using Genode::Signal_handler;
using Genode::Reporter;
using Genode::Interface;
}
namespace Wm {
struct Decorator_nitpicker_session;
struct Decorator_content_callback;
struct Decorator_content_registry;
}
struct Wm::Decorator_content_callback : Interface
{
virtual void content_geometry(Window_registry::Id win_id, Rect rect) = 0;
virtual Nitpicker::View_capability content_view(Window_registry::Id win_id) = 0;
virtual void update_content_child_views(Window_registry::Id win_id) = 0;
};
class Wm::Decorator_content_registry
{
public:
/**
* Exception type
*/
struct Lookup_failed { };
private:
struct Entry : List<Entry>::Element
{
Nitpicker::Session::View_handle const decorator_view_handle;
Window_registry::Id const win_id;
Entry(Nitpicker::Session::View_handle decorator_view_handle,
Window_registry::Id win_id)
:
decorator_view_handle(decorator_view_handle),
win_id(win_id)
{ }
};
List<Entry> _list { };
Allocator &_entry_alloc;
Entry const &_lookup(Nitpicker::Session::View_handle view_handle) const
{
for (Entry const *e = _list.first(); e; e = e->next()) {
if (e->decorator_view_handle == view_handle)
return *e;
}
throw Lookup_failed();
}
void _remove(Entry const &e)
{
_list.remove(&e);
destroy(_entry_alloc, const_cast<Entry *>(&e));
}
public:
Decorator_content_registry(Allocator &entry_alloc)
:
_entry_alloc(entry_alloc)
{ }
~Decorator_content_registry()
{
while (Entry *e = _list.first())
_remove(*e);
}
void insert(Nitpicker::Session::View_handle decorator_view_handle,
Window_registry::Id win_id)
{
Entry *e = new (_entry_alloc) Entry(decorator_view_handle, win_id);
_list.insert(e);
}
/**
* Lookup window ID for a given decorator content view
*
* \throw Lookup_failed
*/
Window_registry::Id lookup(Nitpicker::Session::View_handle view_handle) const
{
return _lookup(view_handle).win_id;
}
bool registered(Nitpicker::Session::View_handle view_handle) const
{
try { lookup(view_handle); return true; } catch (...) { }
return false;
}
/**
* Remove entry
*
* \throw Lookup_failed
*/
void remove(Nitpicker::Session::View_handle view_handle)
{
_remove(_lookup(view_handle));
}
};
struct Wm::Decorator_nitpicker_session : Genode::Rpc_object<Nitpicker::Session>,
private List<Decorator_nitpicker_session>::Element
{
friend class List<Decorator_nitpicker_session>;
using List<Decorator_nitpicker_session>::Element::next;
typedef Nitpicker::View_capability View_capability;
typedef Nitpicker::Session::View_handle View_handle;
Genode::Env &_env;
Genode::Heap _heap { _env.ram(), _env.rm() };
Genode::Ram_allocator &_ram;
Nitpicker::Connection _nitpicker_session { _env, "decorator" };
Genode::Signal_context_capability _mode_sigh { };
typedef Nitpicker::Session::Command_buffer Command_buffer;
Attached_ram_dataspace _command_ds { _ram, _env.rm(), sizeof(Command_buffer) };
Command_buffer &_command_buffer = *_command_ds.local_addr<Command_buffer>();
Point _pointer_position { };
Reporter &_pointer_reporter;
Last_motion &_last_motion;
Input::Session_component &_window_layouter_input;
Decorator_content_callback &_content_callback;
unsigned _key_cnt = 0;
/* XXX don't allocate content-registry entries from heap */
Decorator_content_registry _content_registry { _heap };
Allocator &_md_alloc;
/* Nitpicker::Connection requires a valid input session */
Input::Session_component _dummy_input_component { _env, _env.ram() };
Input::Session_capability _dummy_input_component_cap =
_env.ep().manage(_dummy_input_component);
Signal_handler<Decorator_nitpicker_session>
_input_handler { _env.ep(), *this, &Decorator_nitpicker_session::_handle_input };
/**
* Constructor
*
* \param ep entrypoint used for dispatching signals
*/
Decorator_nitpicker_session(Genode::Env &env,
Genode::Ram_allocator &ram,
Allocator &md_alloc,
Reporter &pointer_reporter,
Last_motion &last_motion,
Input::Session_component &window_layouter_input,
Decorator_content_callback &content_callback)
:
_env(env),
_ram(ram),
_pointer_reporter(pointer_reporter),
_last_motion(last_motion),
_window_layouter_input(window_layouter_input),
_content_callback(content_callback),
_md_alloc(md_alloc)
{
_nitpicker_session.input()->sigh(_input_handler);
}
~Decorator_nitpicker_session()
{
_env.ep().dissolve(_dummy_input_component);
}
void _handle_input()
{
auto report_pointer_position = [&] () {
Reporter::Xml_generator xml(_pointer_reporter, [&] ()
{
xml.attribute("xpos", _pointer_position.x());
xml.attribute("ypos", _pointer_position.y());
});
};
while (_nitpicker_session.input()->pending())
_nitpicker_session.input()->for_each_event([&] (Input::Event const &ev) {
if (ev.press()) _key_cnt++;
if (ev.release()) {
_key_cnt--;
/*
* When returning from a drag operation to idle state, the
* pointer position may have moved to another window
* element. Propagate the least recent pointer position to
* the decorator to update its hover model.
*/
if (_key_cnt == 0)
report_pointer_position();
}
ev.handle_absolute_motion([&] (int x, int y) {
_last_motion = LAST_MOTION_DECORATOR;
_pointer_position = Point(x, y);
/* update pointer model, but only when hovering */
if (_key_cnt == 0)
report_pointer_position();
});
if (ev.hover_leave()) {
/*
* Invalidate pointer as reported to the decorator if the
* pointer moved from a window decoration to a position
* with no window known to the window manager. If the last
* motion referred to one of the regular client session,
* this is not needed because the respective session will
* update the pointer model with the entered position
* already.
*/
if (_last_motion == LAST_MOTION_DECORATOR) {
Reporter::Xml_generator xml(_pointer_reporter, [&] ()
{ });
}
}
_window_layouter_input.submit(ev);
});
}
void _execute_command(Command const &cmd)
{
switch (cmd.opcode) {
case Command::OP_TITLE:
{
unsigned long id = 0;
Genode::ascii_to(cmd.title.title.string(), id);
if (id > 0)
_content_registry.insert(cmd.title.view,
Window_registry::Id(id));
return;
}
case Command::OP_TO_FRONT:
case Command::OP_TO_BACK:
try {
View_handle const view_handle = (cmd.opcode == Command::OP_TO_FRONT)
? cmd.to_front.view
: cmd.to_back.view;
/*
* If the content view is re-stacked, replace it by the real
* window content.
*
* The lookup fails with an exception for non-content views.
* In this case, forward the command.
*/
Window_registry::Id win_id = _content_registry.lookup(view_handle);
/*
* Replace content view originally created by the decorator
* by view that shows the real window content.
*/
Nitpicker::View_capability view_cap =
_content_callback.content_view(win_id);
_nitpicker_session.destroy_view(view_handle);
_nitpicker_session.view_handle(view_cap, view_handle);
_nitpicker_session.enqueue(cmd);
_nitpicker_session.execute();
/*
* Now that the physical content view exists, it is time
* to revisit the child views.
*/
_content_callback.update_content_child_views(win_id);
} catch (Decorator_content_registry::Lookup_failed) {
_nitpicker_session.enqueue(cmd);
}
return;
case Command::OP_GEOMETRY:
try {
/*
* If the content view changes position, propagate the new
* position to the nitpicker service to properly transform
* absolute input coordinates.
*/
Window_registry::Id win_id = _content_registry.lookup(cmd.geometry.view);
_content_callback.content_geometry(win_id, cmd.geometry.rect);
}
catch (Decorator_content_registry::Lookup_failed) { }
/* forward command */
_nitpicker_session.enqueue(cmd);
return;
case Command::OP_OFFSET:
try {
/*
* If non-content views change their offset (if the lookup
* fails), propagate the event
*/
_content_registry.lookup(cmd.geometry.view);
}
catch (Decorator_content_registry::Lookup_failed) {
_nitpicker_session.enqueue(cmd);
}
return;
case Command::OP_BACKGROUND:
case Command::OP_NOP:
_nitpicker_session.enqueue(cmd);
return;
}
}
void upgrade(const char *args)
{
size_t const ram_quota = Arg_string::find_arg(args, "ram_quota").ulong_value(0);
_nitpicker_session.upgrade_ram(ram_quota);
}
/*********************************
** Nitpicker session interface **
*********************************/
Framebuffer::Session_capability framebuffer_session() override
{
return _nitpicker_session.framebuffer_session();
}
Input::Session_capability input_session() override
{
/*
* Deny input to the decorator. User input referring to the
* window decorations is routed to the window manager.
*/
return _dummy_input_component_cap;
}
View_handle create_view(View_handle parent) override
{
return _nitpicker_session.create_view(parent);
}
void destroy_view(View_handle view) override
{
/*
* Reset view geometry when destroying a content view
*/
if (_content_registry.registered(view)) {
Nitpicker::Rect rect(Nitpicker::Point(0, 0), Nitpicker::Area(0, 0));
_nitpicker_session.enqueue<Nitpicker::Session::Command::Geometry>(view, rect);
_nitpicker_session.execute();
}
_nitpicker_session.destroy_view(view);
}
View_handle view_handle(View_capability view_cap, View_handle handle) override
{
return _nitpicker_session.view_handle(view_cap, handle);
}
View_capability view_capability(View_handle view) override
{
return _nitpicker_session.view_capability(view);
}
void release_view_handle(View_handle view) override
{
/* XXX dealloc View_ptr */
_nitpicker_session.release_view_handle(view);
}
Genode::Dataspace_capability command_dataspace() override
{
return _command_ds.cap();
}
void execute() override
{
for (unsigned i = 0; i < _command_buffer.num(); i++) {
try {
_execute_command(_command_buffer.get(i));
}
catch (...) {
Genode::warning("unhandled exception while processing command from decorator");
}
}
_nitpicker_session.execute();
}
Framebuffer::Mode mode() override
{
return _nitpicker_session.mode();
}
void mode_sigh(Genode::Signal_context_capability sigh) override
{
/*
* Remember signal-context capability to keep NOVA from revoking
* transitive delegations of the capability.
*/
_mode_sigh = sigh;
_nitpicker_session.mode_sigh(sigh);
}
void buffer(Framebuffer::Mode mode, bool use_alpha) override
{
/*
* See comment in 'Wm::Nitpicker::Session_component::buffer'.
*/
Nitpicker::Session_client(_env.rm(), _nitpicker_session.cap()).buffer(mode, use_alpha);
}
void focus(Genode::Capability<Nitpicker::Session>) override { }
};
#endif /* _DECORATOR_NITPICKER_H_ */