diff --git a/repos/os/src/server/nitpicker/README b/repos/os/src/server/nitpicker/README
index d9cb871626..e12763f1ac 100644
--- a/repos/os/src/server/nitpicker/README
+++ b/repos/os/src/server/nitpicker/README
@@ -244,6 +244,47 @@ client. This report is useful for a focus-managing component to implement a
focus-on-click policy.
+Multi-monitor support
+~~~~~~~~~~~~~~~~~~~~~
+
+Display drivers obtain pixel data from the nitpicker GUI server using
+nitpicker's capture service. For each connected monitor, the driver creates a
+distinct capture session labeled after the name of the connector. Therefore,
+from nitpicker's perspective, each monitor corresponds to one capture client.
+Each capture client can have a different size, which corresponds to the
+respective display resolution. Together, all capture clients span a panorama,
+which is the bounding box of all capture clients. Each capture client shows a
+part of the panorama.
+
+By default, when configuring nitpicker with an empty '' node, the
+top-left corner of each capture client corresponds to the coordinate origin
+(0, 0) of the panorama. Hence, each client obtains a mirror of the panorama.
+This default policy can be overridden by explicit rules as follows:
+
+!
+!
+!
+!
+!
+
+The policy for the 'eDP-1' connector merely overrides the physical dimensions
+of the display as reported by the driver. This is useful in situations where
+the display's EDID information are incorrect. Apart from that tweak, the
+client obtains the default part of the panorama at the coordinate origin.
+
+The policy for the HDMI-A-1 connector dictates an explicit placement within
+the panorama. So a data projector connected via HDMI shows a mere window of
+the panorama where the top-left corner corresponds to the panorama position
+(1024, 0). The client won't observe any pixels outside the specified window.
+It is possible to specify only a subset of attributes. E.g., by only
+specifying the 'xpos', the width and height remain unconstrained.
+
+The default policy tells nitpicker to regard any other capture client as a
+mirror of the panorama's coordinate origin. If absent, a client with no
+policy, won't obtain any picture.
+
+
Cascaded usage scenarios
~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/repos/os/src/server/nitpicker/capture_session.h b/repos/os/src/server/nitpicker/capture_session.h
index 2ef70eb120..96cc857766 100644
--- a/repos/os/src/server/nitpicker/capture_session.h
+++ b/repos/os/src/server/nitpicker/capture_session.h
@@ -36,6 +36,65 @@ class Nitpicker::Capture_session : public Session_object
virtual void capture_requested(Label const &) = 0;
};
+ struct Policy
+ {
+ template
+ struct Attr
+ {
+ bool _defined { };
+ T _value { };
+
+ Attr() { }
+
+ Attr(T value) : _defined(true), _value(value) { }
+
+ Attr(Xml_node const node, auto const &attr)
+ {
+ if (node.has_attribute(attr)) {
+ _value = node.attribute_value(attr, T { });
+ _defined = true;
+ }
+ }
+
+ /**
+ * Return defined attribute value, or default value
+ */
+ T or_default(T def) const { return _defined ? _value : def; }
+ };
+
+ Attr x, y; /* placement within panorama */
+ Attr w, h; /* capture contraints */
+ Attr w_mm, h_mm; /* physical size overrides */
+
+ static Policy from_xml(Xml_node const &policy)
+ {
+ return { .x = { policy, "xpos" },
+ .y = { policy, "ypos" },
+ .w = { policy, "width" },
+ .h = { policy, "height" },
+ .w_mm = { policy, "width_mm" },
+ .h_mm = { policy, "height_mm" } };
+ }
+
+ static Policy unconstrained() { return { }; }
+
+ static Policy blocked()
+ {
+ Policy result { };
+ result.w = 0, result.h = 0;
+ return result;
+ }
+ };
+
+ static void gen_attr(Xml_generator &xml, Rect const rect)
+ {
+ if (rect.x1()) xml.attribute("xpos", rect.x1());
+ if (rect.y1()) xml.attribute("ypos", rect.y1());
+
+ xml.attribute("width", rect.w());
+ xml.attribute("height", rect.h());
+ }
+
private:
Env &_env;
@@ -46,6 +105,8 @@ class Nitpicker::Capture_session : public Session_object
View_stack const &_view_stack;
+ Policy _policy = Policy::blocked();
+
Buffer_attr _buffer_attr { };
Constructible _buffer { };
@@ -68,6 +129,18 @@ class Nitpicker::Capture_session : public Session_object
}
}
+ Point _anchor_point() const
+ {
+ return { .x = _policy.x.or_default(0),
+ .y = _policy.y.or_default(0) };
+ }
+
+ Area _area_bounds() const
+ {
+ return { .w = _policy.w.or_default(_buffer_attr.px.w),
+ .h = _policy.h.or_default(_buffer_attr.px.h) };
+ }
+
public:
Capture_session(Env &env,
@@ -83,7 +156,7 @@ class Nitpicker::Capture_session : public Session_object
_handler(handler),
_view_stack(view_stack)
{
- _dirty_rect.mark_as_dirty(Rect(Point(0, 0), view_stack.size()));
+ _dirty_rect.mark_as_dirty(view_stack.bounding_box());
}
~Capture_session() { }
@@ -93,7 +166,10 @@ class Nitpicker::Capture_session : public Session_object
** Interface used by 'Nitpicker::Main' **
*****************************************/
- Area buffer_size() const { return _buffer_attr.px; }
+ /**
+ * Geometry within the panorama, depending on policy and client buffer
+ */
+ Rect bounding_box() const { return { _anchor_point(), _area_bounds() }; }
void mark_as_damaged(Rect rect)
{
@@ -107,12 +183,17 @@ class Nitpicker::Capture_session : public Session_object
Signal_transmitter(_screen_size_sigh).submit();
}
+ void apply_policy(Policy const &policy) { _policy = policy; }
+
/*******************************
** Capture session interface **
*******************************/
- Area screen_size() const override { return _view_stack.size(); }
+ Area screen_size() const override
+ {
+ return Rect::intersect(_view_stack.bounding_box(), bounding_box()).area;
+ }
void screen_size_sigh(Signal_context_capability sigh) override
{
@@ -131,7 +212,7 @@ class Nitpicker::Capture_session : public Session_object
_buffer_attr = { };
- if (attr.px.count() == 0) {
+ if (!attr.px.valid()) {
_buffer.destruct();
return result;
}
@@ -146,7 +227,7 @@ class Nitpicker::Capture_session : public Session_object
_handler.capture_buffer_size_changed();
/* report complete buffer as dirty on next call of 'capture_at' */
- mark_as_damaged({ { 0, 0 }, attr.px });
+ mark_as_damaged({ _anchor_point(), attr.px });
return result;
}
@@ -159,18 +240,19 @@ class Nitpicker::Capture_session : public Session_object
return Dataspace_capability();
}
- Affected_rects capture_at(Point pos) override
+ Affected_rects capture_at(Point const pos) override
{
_handler.capture_requested(label());
if (!_buffer.constructed())
return Affected_rects { };
- using Pixel = Pixel_rgb888;
+ Canvas canvas { _buffer->local_addr(),
+ _anchor_point() + pos, _buffer_attr.px };
- Canvas canvas = { _buffer->local_addr(), pos, _buffer_attr.px };
+ canvas.clip(Rect::intersect(bounding_box(), _view_stack.bounding_box()));
- Rect const buffer_rect(Point(0, 0), _buffer_attr.px);
+ Rect const buffer_rect { { }, _buffer_attr.px };
Affected_rects affected { };
unsigned i = 0;
@@ -179,7 +261,7 @@ class Nitpicker::Capture_session : public Session_object
_view_stack.draw(canvas, rect);
if (i < Affected_rects::NUM_RECTS) {
- Rect const translated(rect.p1() - pos, rect.area);
+ Rect const translated(rect.p1() - _anchor_point() - pos, rect.area);
Rect const clipped = Rect::intersect(translated, buffer_rect);
affected.rects[i++] = clipped;
}
diff --git a/repos/os/src/server/nitpicker/domain_registry.h b/repos/os/src/server/nitpicker/domain_registry.h
index 3d20ce40cf..486f3c2cbd 100644
--- a/repos/os/src/server/nitpicker/domain_registry.h
+++ b/repos/os/src/server/nitpicker/domain_registry.h
@@ -64,17 +64,16 @@ class Nitpicker::Domain_registry
_origin(origin), _layer(layer), _offset(offset), _area(area)
{ }
- Point _corner(Area const screen_area) const
+ Point _corner(Rect const rect) const
{
switch (_origin) {
- case Origin::POINTER: return Point(0, 0);
- case Origin::TOP_LEFT: return Point(0, 0);
- case Origin::TOP_RIGHT: return Point(screen_area.w, 0);
- case Origin::BOTTOM_LEFT: return Point(0, screen_area.h);
- case Origin::BOTTOM_RIGHT: return Point(screen_area.w,
- screen_area.h);
+ case Origin::POINTER: return { 0, 0 };
+ case Origin::TOP_LEFT: return { rect.x1(), rect.y1() };
+ case Origin::TOP_RIGHT: return { rect.x2(), rect.y1() };
+ case Origin::BOTTOM_LEFT: return { rect.x1(), rect.y2() };
+ case Origin::BOTTOM_RIGHT: return { rect.x2(), rect.y2() };
}
- return Point(0, 0);
+ return { 0, 0 };
}
public:
@@ -95,22 +94,22 @@ class Nitpicker::Domain_registry
bool focus_transient() const { return _focus == Focus::TRANSIENT; }
bool origin_pointer() const { return _origin == Origin::POINTER; }
- Point phys_pos(Point pos, Area screen_area) const
+ Point phys_pos(Point pos, Rect panorama) const
{
- return pos + _corner(screen_area) + _offset;
+ return pos + _corner(panorama) + _offset;
}
- Area screen_area(Area phys_screen_area) const
+ Rect screen_rect(Area screen_area) const
{
- int const w = _area.x > 0
- ? _area.x
- : max(0, (int)phys_screen_area.w + _area.x);
+ /* align value to zero or to limit, depending on its sign */
+ auto aligned = [&] (unsigned limit, int v)
+ {
+ return unsigned((v > 0) ? v : max(0, int(limit) + v));
+ };
- int const h = _area.y > 0
- ? _area.y
- : max(0, (int)phys_screen_area.h + _area.y);
-
- return Area(w, h);
+ return { .at = _offset,
+ .area = { .w = aligned(screen_area.w, _area.x),
+ .h = aligned(screen_area.h, _area.y) } };
}
};
diff --git a/repos/os/src/server/nitpicker/gui_session.cc b/repos/os/src/server/nitpicker/gui_session.cc
index 16cdcf927e..ba705866a0 100644
--- a/repos/os/src/server/nitpicker/gui_session.cc
+++ b/repos/os/src/server/nitpicker/gui_session.cc
@@ -64,7 +64,7 @@ void Gui_session::_execute_command(Command const &command)
/* transpose position of top-level views by vertical session offset */
if (view.top_level())
- pos = _phys_pos(pos, _view_stack.size());
+ pos = _phys_pos(pos, _view_stack.bounding_box());
_view_stack.geometry(view, Rect(pos, args.rect.area));
});
@@ -175,7 +175,7 @@ void Gui_session::submit_input_event(Input::Event e)
{
using namespace Input;
- Point const origin_offset = _phys_pos(Point(0, 0), _view_stack.size());
+ Point const origin_offset = _phys_pos({ 0, 0 }, _view_stack.bounding_box());
/*
* Transpose absolute coordinates by session-specific vertical offset.
@@ -377,14 +377,14 @@ void Gui_session::execute()
Framebuffer::Mode Gui_session::mode()
{
- Area const screen = screen_area(_view_stack.size());
+ Rect const screen = screen_rect(_view_stack.bounding_box().area);
/*
* Return at least a size of 1x1 to spare the clients the need to handle
* the special case of 0x0, which can happen at boot time before the
* framebuffer driver is running.
*/
- return { .area = { max(screen.w, 1u), max(screen.h, 1u) },
+ return { .area = { max(screen.w(), 1u), max(screen.h(), 1u) },
.alpha = uses_alpha() };
}
diff --git a/repos/os/src/server/nitpicker/gui_session.h b/repos/os/src/server/nitpicker/gui_session.h
index 6021c6a17e..40d5b3c50f 100644
--- a/repos/os/src/server/nitpicker/gui_session.h
+++ b/repos/os/src/server/nitpicker/gui_session.h
@@ -153,14 +153,14 @@ class Nitpicker::Gui_session : public Session_object,
Gui_session *_forwarded_focus = nullptr;
/**
- * Calculate session-local coordinate to physical screen position
+ * Calculate session-local coordinate to position within panorama
*
- * \param pos coordinate in session-local coordinate system
- * \param screen_area session-local screen size
+ * \param pos coordinate in session-local coordinate system
+ * \param rect geometry within panorama
*/
- Point _phys_pos(Point pos, Area screen_area) const
+ Point _phys_pos(Point pos, Rect panorama) const
{
- return _domain ? _domain->phys_pos(pos, screen_area) : Point(0, 0);
+ return _domain ? _domain->phys_pos(pos, panorama) : Point(0, 0);
}
void _execute_command(Command const &);
@@ -320,13 +320,11 @@ class Nitpicker::Gui_session : public Session_object,
void visible(bool visible) { _visible = visible; }
/**
- * Return session-local screen area
- *
- * \param phys_pos size of physical screen
+ * Return session-local screen geometry
*/
- Area screen_area(Area phys_area) const
+ Rect screen_rect(Area screen_area) const
{
- return _domain ? _domain->screen_area(phys_area) : Area(0, 0);
+ return _domain ? _domain->screen_rect(screen_area) : Rect { };
}
void reset_domain() { _domain = nullptr; }
diff --git a/repos/os/src/server/nitpicker/main.cc b/repos/os/src/server/nitpicker/main.cc
index 2cfc043b68..37c2c01c8a 100644
--- a/repos/os/src/server/nitpicker/main.cc
+++ b/repos/os/src/server/nitpicker/main.cc
@@ -201,27 +201,38 @@ class Nitpicker::Gui_root : public Root_component
class Nitpicker::Capture_root : public Root_component
{
+ public:
+
+ struct Action : Interface
+ {
+ virtual void capture_client_appeared_or_disappeared() = 0;
+ };
+
private:
using Sessions = Registry>;
Env &_env;
+ Action &_action;
Sessions _sessions { };
View_stack const &_view_stack;
Capture_session::Handler &_handler;
- Area _fallback_bounding_box { 0, 0 };
+ Rect _fallback_bounding_box { };
protected:
Capture_session *_create_session(const char *args) override
{
- return new (md_alloc())
+ Capture_session &session = *new (md_alloc())
Registered(_sessions, _env,
session_resources_from_args(args),
session_label_from_args(args),
session_diag_from_args(args),
_handler, _view_stack);
+
+ _action.capture_client_appeared_or_disappeared();
+ return &session;
}
void _upgrade_session(Capture_session *s, const char *args) override
@@ -237,12 +248,11 @@ class Nitpicker::Capture_root : public Root_component
* mode switches when the only capture client temporarily
* disappears (driver restart).
*/
- _fallback_bounding_box = session->buffer_size();
+ _fallback_bounding_box = session->bounding_box();
Genode::destroy(md_alloc(), session);
- /* shrink screen according to the remaining output back ends */
- _handler.capture_buffer_size_changed();
+ _action.capture_client_appeared_or_disappeared();
}
public:
@@ -251,26 +261,74 @@ class Nitpicker::Capture_root : public Root_component
* Constructor
*/
Capture_root(Env &env,
+ Action &action,
Allocator &md_alloc,
View_stack const &view_stack,
Capture_session::Handler &handler)
:
Root_component(&env.ep().rpc_ep(), &md_alloc),
- _env(env), _view_stack(view_stack), _handler(handler)
+ _env(env), _action(action), _view_stack(view_stack), _handler(handler)
{ }
- /**
- * Determine the size of the bounding box of all capture pixel buffers
- */
- Area bounding_box() const
+ void apply_config(Xml_node const &config)
{
- Area result = { 0, 0 };
- bool any_session_present = false;
- _sessions.for_each([&] (Capture_session const &session) {
- any_session_present = true;
- result = max_area(result, session.buffer_size()); });
+ using Policy = Capture_session::Policy;
- return any_session_present ? result : _fallback_bounding_box;
+ if (config.num_sub_nodes() == 0) {
+
+ /* if no policies are defined, mirror with no constraints */
+ _sessions.for_each([&] (Capture_session &session) {
+ session.apply_policy(Policy::unconstrained()); });
+
+ } else {
+
+ /* apply constraits per session */
+ _sessions.for_each([&] (Capture_session &session) {
+ with_matching_policy(session.label(), config,
+ [&] (Xml_node const &policy) {
+ session.apply_policy(Policy::from_xml(policy));
+ },
+ [&] { session.apply_policy(Policy::blocked()); }); });
+ }
+ }
+
+ /**
+ * Determine the bounding box of all capture clients
+ */
+ Rect bounding_box() const
+ {
+ Rect bb { };
+ _sessions.for_each([&] (Capture_session const &session) {
+ bb = Rect::compound(bb, session.bounding_box()); });
+
+ return bb.valid() ? bb : _fallback_bounding_box;
+ }
+
+ /**
+ * Return true if specified position is suited as pointer position
+ */
+ bool visible(Pointer const pointer) const
+ {
+ bool result = false;
+ pointer.with_result(
+ [&] (Point const p) {
+ _sessions.for_each([&] (Capture_session const &session) {
+ if (!result && session.bounding_box().contains(p))
+ result = true; }); },
+ [&] (Nowhere) { });
+ return result;
+ }
+
+ /**
+ * Return position suitable for the initial pointer position
+ */
+ Pointer any_visible_pointer_position() const
+ {
+ Pointer result = Nowhere { };
+ _sessions.for_each([&] (Capture_session const &session) {
+ if (session.bounding_box().valid())
+ result = session.bounding_box().center({ 1, 1 }); });
+ return result;
}
/**
@@ -290,22 +348,12 @@ class Nitpicker::Capture_root : public Root_component
void report_displays(Xml_generator &xml) const
{
- bool any_session_present = false;
- _sessions.for_each([&] (Capture_session const &) {
- any_session_present = true; });
+ Capture_session::gen_attr(xml, _view_stack.bounding_box());
- if (!any_session_present)
- return;
-
- Area const size = bounding_box();
-
- if (size.count() == 0)
- return;
-
- xml.node("display", [&] () {
- xml.attribute("width", size.w);
- xml.attribute("height", size.h);
- });
+ _sessions.for_each([&] (Capture_session const &capture) {
+ xml.node("capture", [&] {
+ xml.attribute("name", capture.label());
+ Capture_session::gen_attr(xml, capture.bounding_box()); }); });
}
};
@@ -361,7 +409,10 @@ class Nitpicker::Event_root : public Root_component
struct Nitpicker::Main : Focus_updater, Hover_updater,
View_stack::Damage,
Capture_session::Handler,
- Event_session::Handler
+ Event_session::Handler,
+ Capture_root::Action,
+ User_state::Action
+
{
Env &_env;
@@ -425,7 +476,7 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
Canvas _screen { _fb_ds.local_addr(), Point(0, 0), _mode.area };
- Area const _size = _screen.size();
+ Rect const _rect { { 0, 0 }, _screen.size() };
using Dirty_rect = Genode::Dirty_rect;
@@ -458,7 +509,7 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
{
_fb.mode_sigh(_main._fb_screen_mode_handler);
_fb.sync_sigh(_sync_handler);
- mark_as_dirty(Rect { Point { 0, 0 }, _size });
+ mark_as_dirty(_rect);
}
~Framebuffer_screen()
@@ -474,6 +525,10 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
if (_main._now().ms - _previous_sync.ms > 40)
_handle_sync();
}
+
+ bool visible(Point p) const { return _rect.contains(p); }
+
+ Point anywhere() const { return _rect.center({ 1, 1 }); };
};
bool _request_framebuffer = false;
@@ -481,6 +536,19 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
Constructible _fb_screen { };
+ bool _visible_at_fb_screen(Pointer pointer) const
+ {
+ return pointer.convert(
+ [&] (Point p) { return _fb_screen.constructed() && _fb_screen->visible(p); },
+ [&] (Nowhere) { return false; });
+ }
+
+ Pointer _anywhere_at_fb_screen() const
+ {
+ return _fb_screen.constructed() ? Pointer { _fb_screen->anywhere() }
+ : Pointer { Nowhere { } };
+ }
+
Signal_handler _fb_screen_mode_handler {
_env.ep(), *this, &Main::_reconstruct_fb_screen };
@@ -518,7 +586,7 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
Focus _focus { };
View_stack _view_stack { _focus, _font, *this };
- User_state _user_state { _focus, _global_keys, _view_stack };
+ User_state _user_state { *this, _focus, _global_keys, _view_stack };
View_owner _global_view_owner { };
@@ -550,7 +618,7 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
_builtin_background, _sliced_heap,
_focus_reporter, *this, *this };
- Capture_root _capture_root { _env, _sliced_heap, _view_stack, *this };
+ Capture_root _capture_root { _env, *this, _sliced_heap, _view_stack, *this };
Event_root _event_root { _env, _sliced_heap, *this };
@@ -575,7 +643,7 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
void _update_input_connection()
{
- bool const output_present = (_view_stack.size().count() > 0);
+ bool const output_present = (_view_stack.bounding_box().valid());
_input.conditional(_request_input && output_present, _env, *this);
}
@@ -589,18 +657,21 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
* present output back ends.
*/
- Area new_size { 0, 0 };
+ Rect new_bb { };
if (_fb_screen.constructed())
- new_size = max_area(new_size, _fb_screen->_size);
+ new_bb = Rect::compound(new_bb, Rect { _fb_screen->_rect });
- new_size = max_area(new_size, _capture_root.bounding_box());
+ new_bb = Rect::compound(new_bb, _capture_root.bounding_box());
- bool const size_changed = (new_size != _view_stack.size());
+ bool const size_changed = (new_bb != _view_stack.bounding_box());
if (size_changed) {
- _view_stack.size(new_size);
- _user_state.sanitize_pointer_position();
+ _view_stack.bounding_box(new_bb);
+
+ if (!_user_state.pointer().ok())
+ _user_state.pointer(_capture_root.any_visible_pointer_position());
+
_update_pointer_position();
_capture_root.screen_size_changed();
@@ -626,6 +697,22 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
s->submit_sync();
}
+ /**
+ * User_state::Action interface
+ */
+ Pointer sanitized_pointer_position(Pointer const orig_pos, Point pos) override
+ {
+ if (_capture_root.visible(pos) || _visible_at_fb_screen(pos))
+ return pos;
+
+ if (_capture_root.visible(orig_pos) || _visible_at_fb_screen(orig_pos))
+ return orig_pos;
+
+ Pointer const captured_pos = _capture_root.any_visible_pointer_position();
+
+ return captured_pos.ok() ? captured_pos : _anywhere_at_fb_screen();
+ }
+
/**
* Focus_updater interface
*
@@ -652,15 +739,25 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
* manually to turn the initial configuration into effect.
*/
void _handle_config();
+ void _apply_capture_config();
- Signal_handler _config_handler = { _env.ep(), *this, &Main::_handle_config };
+ Signal_handler _config_handler { _env.ep(), *this, &Main::_handle_config };
+
+ /**
+ * Capture_root::Action interface
+ */
+ void capture_client_appeared_or_disappeared() override
+ {
+ _apply_capture_config();
+ capture_buffer_size_changed();
+ }
/**
* Signal handler for externally triggered focus changes
*/
void _handle_focus();
- Signal_handler _focus_handler = { _env.ep(), *this, &Main::_handle_focus };
+ Signal_handler _focus_handler { _env.ep(), *this, &Main::_handle_focus };
/**
* Event_session::Handler interface
@@ -692,7 +789,10 @@ struct Nitpicker::Main : Focus_updater, Hover_updater,
void _update_pointer_position()
{
- _view_stack.geometry(_pointer_origin, Rect(_user_state.pointer_pos(), Area{}));
+ _user_state.pointer().with_result(
+ [&] (Point p) {
+ _view_stack.geometry(_pointer_origin, Rect(p, Area{})); },
+ [&] (Nowhere) { });
}
Main(Env &env) : _env(env)
@@ -836,6 +936,15 @@ void Nitpicker::Main::_handle_focus()
}
+void Nitpicker::Main::_apply_capture_config()
+{
+ /* propagate capture policies */
+ _config_rom.xml().with_optional_sub_node("capture",
+ [&] (Xml_node const &capture) {
+ _capture_root.apply_config(capture); });
+}
+
+
void Nitpicker::Main::_handle_config()
{
_config_rom.update();
@@ -859,6 +968,8 @@ void Nitpicker::Main::_handle_config()
configure_reporter(config, _clicked_reporter);
configure_reporter(config, _displays_reporter);
+ _apply_capture_config();
+
/* update domain registry and session policies */
for (Gui_session *s = _session_list.first(); s; s = s->next())
s->reset_domain();
@@ -927,12 +1038,8 @@ void Nitpicker::Main::_report_displays()
return;
Reporter::Xml_generator xml(_displays_reporter, [&] () {
- if (_fb_screen.constructed()) {
- xml.node("display", [&] () {
- xml.attribute("width", _fb_screen->_size.w);
- xml.attribute("height", _fb_screen->_size.h);
- });
- }
+ if (_fb_screen.constructed())
+ xml.node("display", [&] { gen_attr(xml, _fb_screen->_rect); });
_capture_root.report_displays(xml);
});
diff --git a/repos/os/src/server/nitpicker/types.h b/repos/os/src/server/nitpicker/types.h
index 9ec5042c94..2350eace1a 100644
--- a/repos/os/src/server/nitpicker/types.h
+++ b/repos/os/src/server/nitpicker/types.h
@@ -16,6 +16,7 @@
/* Genode includes */
#include
+#include
#include
#include
#include
@@ -39,6 +40,23 @@ namespace Nitpicker {
{
return Area(max(a1.w, a2.w), max(a1.h, a2.h));
}
+
+ struct Nowhere { };
+
+ using Pointer = Attempt;
+
+ static inline void gen_attr(Xml_generator &xml, Point const point)
+ {
+ if (point.x) xml.attribute("xpos", point.x);
+ if (point.y) xml.attribute("ypos", point.y);
+ }
+
+ static inline void gen_attr(Xml_generator &xml, Rect const rect)
+ {
+ gen_attr(xml, rect.at);
+ if (rect.w()) xml.attribute("width", rect.w());
+ if (rect.h()) xml.attribute("height", rect.h());
+ }
}
#endif /* _TYPES_H_ */
diff --git a/repos/os/src/server/nitpicker/user_state.cc b/repos/os/src/server/nitpicker/user_state.cc
index e36e0c01d2..3d12891ad2 100644
--- a/repos/os/src/server/nitpicker/user_state.cc
+++ b/repos/os/src/server/nitpicker/user_state.cc
@@ -94,23 +94,26 @@ void User_state::_handle_input_event(Input::Event ev)
/* transparently convert relative into absolute motion event */
ev.handle_relative_motion([&] (int x, int y) {
-
- int const ox = _pointer_pos.x,
- oy = _pointer_pos.y;
-
- int const ax = max(0, min((int)_view_stack.size().w - 1, ox + x)),
- ay = max(0, min((int)_view_stack.size().h - 1, oy + y));
-
- ev = Absolute_motion{ax, ay};
+ _pointer.with_result(
+ [&] (Point orig_pos) {
+ Point const p = orig_pos + Point { x, y };
+ ev = Absolute_motion { p.x, p.y }; },
+ [&] (Nowhere) { });
});
/* respond to motion events by updating the pointer position */
ev.handle_absolute_motion([&] (int x, int y) {
- _pointer_pos = Point(x, y); });
+ _try_move_pointer({ x, y });
+
+ /* enforce sanitized position (prevent move to invisible areas) */
+ _pointer.with_result(
+ [&] (Point p) { ev = Absolute_motion { p.x, p.y }; },
+ [&] (Nowhere) { ev = { }; });
+ });
/* let pointer position correspond to most recent touch position */
ev.handle_touch([&] (Input::Touch_id, float x, float y) {
- _pointer_pos = Point((int)x, (int)y); });
+ _try_move_pointer({ int(x), int(y) }); });
/* track key states, drop double press/release events */
{
@@ -188,9 +191,11 @@ void User_state::_handle_input_event(Input::Event ev)
_focused->submit_input_event(Focus_leave());
if (_hovered) {
- _hovered->submit_input_event(Absolute_motion{_pointer_pos.x,
- _pointer_pos.y});
- _hovered->submit_input_event(Focus_enter());
+ _pointer.with_result(
+ [&] (Point p) {
+ _hovered->submit_input_event(Absolute_motion{p.x, p.y});
+ _hovered->submit_input_event(Focus_enter()); },
+ [&] (Nowhere) { });
}
if (_hovered->has_transient_focusable_domain()) {
@@ -311,7 +316,7 @@ void User_state::_handle_input_event(Input::Event ev)
User_state::Handle_input_result
User_state::handle_input_events(Input_batch batch)
{
- Point const old_pointer_pos = _pointer_pos;
+ Pointer const old_pointer = _pointer;
View_owner * const old_hovered = _hovered;
View_owner const * const old_focused = _focused;
View_owner const * const old_input_receiver = _input_receiver;
@@ -389,13 +394,23 @@ User_state::handle_input_events(Input_batch batch)
_last_clicked_redeliver = false;
}
+ auto pointer_changed = [&]
+ {
+ return old_pointer.convert(
+ [&] (Point const old) {
+ return _pointer.convert(
+ [&] (Point const p) { return p != old; },
+ [&] (Nowhere) { return true; }); },
+ [&] (Nowhere) { return _pointer.ok(); });
+ };
+
return {
.hover_changed = _hovered != old_hovered,
.focus_changed = (_focused != old_focused) ||
(_input_receiver != old_input_receiver),
.key_state_affected = key_state_affected,
.button_activity = button_activity,
- .motion_activity = (_pointer_pos != old_pointer_pos) || touch_occurred,
+ .motion_activity = pointer_changed() || touch_occurred,
.key_pressed = _key_pressed(),
.last_clicked_changed = last_clicked_changed
};
@@ -411,8 +426,8 @@ void User_state::report_keystate(Xml_generator &xml) const
void User_state::report_pointer_position(Xml_generator &xml) const
{
- xml.attribute("xpos", _pointer_pos.x);
- xml.attribute("ypos", _pointer_pos.y);
+ _pointer.with_result([&] (Point p) { gen_attr(xml, p); },
+ [&] (Nowhere) { });
}
@@ -486,9 +501,12 @@ User_state::Update_hover_result User_state::update_hover()
return { .hover_changed = false };
View_owner * const old_hovered = _hovered;
- View const * const pointed_view = _view_stack.find_view(_pointer_pos);
- _hovered = pointed_view ? &pointed_view->owner() : nullptr;
+ _hovered = _pointer.convert(
+ [&] (Point const p) {
+ View const * const pointed_view = _view_stack.find_view(p);
+ return pointed_view ? &pointed_view->owner() : nullptr; },
+ [&] (Nowhere) { return nullptr; });
/*
* Deliver a leave event if pointed-to session changed, notify newly
@@ -499,8 +517,10 @@ User_state::Update_hover_result User_state::update_hover()
old_hovered->submit_input_event(Hover_leave());
if (_hovered)
- _hovered->submit_input_event(Absolute_motion{_pointer_pos.x,
- _pointer_pos.y});
+ _pointer.with_result(
+ [&] (Point p) {
+ _hovered->submit_input_event(Absolute_motion{p.x, p.y}); },
+ [&] (Nowhere) { });
}
return { .hover_changed = (_hovered != old_hovered) };
diff --git a/repos/os/src/server/nitpicker/user_state.h b/repos/os/src/server/nitpicker/user_state.h
index 821017f027..083f2f66f9 100644
--- a/repos/os/src/server/nitpicker/user_state.h
+++ b/repos/os/src/server/nitpicker/user_state.h
@@ -29,6 +29,19 @@ namespace Nitpicker { class User_state; }
class Nitpicker::User_state
{
+ public:
+
+ struct Action : Interface
+ {
+ /**
+ * Return the pointer position when attempting to move to 'pos'
+ *
+ * This policy hook enables the restriction of pointer movements
+ * to those areas that are captured.
+ */
+ virtual Pointer sanitized_pointer_position(Pointer orig, Point pos) = 0;
+ };
+
private:
/*
@@ -65,6 +78,8 @@ class Nitpicker::User_state
*/
bool _focus_via_click = true;
+ Action &_action;
+
/**
* Input-focus information propagated to the view stack
*/
@@ -80,16 +95,10 @@ class Nitpicker::User_state
*/
View_stack &_view_stack;
- /**
- * True once the initial screen size becomes known and used as the
- * initial (centered) pointer position.
- */
- bool _initial_pointer_position_defined = false;
-
/*
* Current pointer position
*/
- Point _pointer_pos { };
+ Pointer _pointer = Nowhere { };
/*
* Currently pointed-at view owner
@@ -199,6 +208,11 @@ class Nitpicker::User_state
}
}
+ void _try_move_pointer(Point next)
+ {
+ _pointer = _action.sanitized_pointer_position(_pointer, next);
+ }
+
public:
/**
@@ -207,29 +221,16 @@ class Nitpicker::User_state
* \param focus exported focus information, to be consumed by the
* view stack to tailor its view drawing operations
*/
- User_state(Focus &focus, Global_keys &global_keys, View_stack &view_stack)
+ User_state(Action &action, Focus &focus, Global_keys &global_keys,
+ View_stack &view_stack)
:
- _focus(focus), _global_keys(global_keys), _view_stack(view_stack)
+ _action(action), _focus(focus), _global_keys(global_keys),
+ _view_stack(view_stack)
{ }
- /**
- * Called whenever the view-stack size has changed
- */
- void sanitize_pointer_position()
- {
- Area const screen_size = _view_stack.size();
+ void pointer(Pointer p) { _pointer = p; }
- /* center pointer initially */
- if (!_initial_pointer_position_defined) {
- _pointer_pos = Point(screen_size.w/2, screen_size.h/2);
- _initial_pointer_position_defined = true;
- }
-
- /* ensure that pointer remains within screen boundaries */
- if (screen_size.count() > 0)
- _pointer_pos = Point(min((int)screen_size.w - 1, _pointer_pos.x),
- min((int)screen_size.h - 1, _pointer_pos.y));
- }
+ Pointer pointer() const { return _pointer; }
/****************************************
@@ -272,8 +273,6 @@ class Nitpicker::User_state
void report_focused_view_owner(Xml_generator &, bool button_active) const;
void report_last_clicked_view_owner(Xml_generator &) const;
- Point pointer_pos() { return _pointer_pos; }
-
/**
* Enable/disable direct focus changes by clicking on a client
*/
diff --git a/repos/os/src/server/nitpicker/view_stack.cc b/repos/os/src/server/nitpicker/view_stack.cc
index d76235655c..5012f9be34 100644
--- a/repos/os/src/server/nitpicker/view_stack.cc
+++ b/repos/os/src/server/nitpicker/view_stack.cc
@@ -145,7 +145,7 @@ void View_stack::_place_labels(Rect rect)
Rect old = view->label_rect(), best;
/* calculate best visible label position */
- Rect rect = Rect::intersect(Rect(Point(), _size), view_rect);
+ Rect rect = Rect::intersect(_bounding_box, view_rect);
if (start) _optimize_label_rec(start, view, rect, &best);
/*
@@ -239,13 +239,13 @@ void View_stack::geometry(View &view, Rect const rect)
* views. The 'refresh_view' function takes care to constrain the
* refresh to the actual view geometry.
*/
- refresh_view(view, Rect(Point(), _size));
+ refresh_view(view, _bounding_box);
/* change geometry */
view.geometry(Rect(rect));
/* refresh new view geometry */
- refresh_view(view, Rect(Point(), _size));
+ refresh_view(view, _bounding_box);
Rect const compound = Rect::compound(old_outline, _outline(view));
@@ -259,7 +259,7 @@ void View_stack::buffer_offset(View &view, Point const buffer_off)
{
view.buffer_off(buffer_off);
- refresh_view(view, Rect(Point(), _size));
+ refresh_view(view, _bounding_box);
}
diff --git a/repos/os/src/server/nitpicker/view_stack.h b/repos/os/src/server/nitpicker/view_stack.h
index 612c71fe1a..510846bc12 100644
--- a/repos/os/src/server/nitpicker/view_stack.h
+++ b/repos/os/src/server/nitpicker/view_stack.h
@@ -32,7 +32,7 @@ class Nitpicker::View_stack
private:
- Area _size { };
+ Rect _bounding_box { };
Focus &_focus;
Font const &_font;
List _views { };
@@ -99,11 +99,11 @@ class Nitpicker::View_stack
/**
* Return size
*/
- Area size() const { return _size; }
+ Rect bounding_box() const { return _bounding_box; }
- void size(Area size)
+ void bounding_box(Rect rect)
{
- _size = size;
+ _bounding_box = rect;
update_all_views();
}
@@ -128,10 +128,8 @@ class Nitpicker::View_stack
*/
void update_all_views()
{
- Rect const whole_screen(Point(), _size);
-
- _place_labels(whole_screen);
- _damage.mark_as_damaged(whole_screen);
+ _place_labels(_bounding_box);
+ _damage.mark_as_damaged(_bounding_box);
}
/**