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 '<capture/>' 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: + +! <capture> +! <policy label="intel_fb -> eDP-1" width_mm="160" height_mm="90" /> +! <policy label="intel_fb -> HDMI-A-1" xpos="1024" ypos="0" +! width="1280" height="800"/> +! <default-policy/> +! </capture> + +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<Capture::Session> virtual void capture_requested(Label const &) = 0; }; + struct Policy + { + template <typename T> + 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<int> x, y; /* placement within panorama */ + Attr<unsigned> w, h; /* capture contraints */ + Attr<unsigned> 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<Capture::Session> View_stack const &_view_stack; + Policy _policy = Policy::blocked(); + Buffer_attr _buffer_attr { }; Constructible<Attached_ram_dataspace> _buffer { }; @@ -68,6 +129,18 @@ class Nitpicker::Capture_session : public Session_object<Capture::Session> } } + 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<Capture::Session> _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<Capture::Session> ** 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<Capture::Session> 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<Capture::Session> _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<Capture::Session> _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<Capture::Session> 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<Pixel_rgb888> canvas { _buffer->local_addr<Pixel_rgb888>(), + _anchor_point() + pos, _buffer_attr.px }; - Canvas<Pixel> canvas = { _buffer->local_addr<Pixel>(), 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<Capture::Session> _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>, 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<Gui::Session>, 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<Gui_session> class Nitpicker::Capture_root : public Root_component<Capture_session> { + public: + + struct Action : Interface + { + virtual void capture_client_appeared_or_disappeared() = 0; + }; + private: using Sessions = Registry<Registered<Capture_session>>; 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<Capture_session>(_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<Capture_session> * 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<Capture_session> * Constructor */ Capture_root(Env &env, + Action &action, Allocator &md_alloc, View_stack const &view_stack, Capture_session::Handler &handler) : Root_component<Capture_session>(&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<Capture_session> 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<Event_session> 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<PT> _screen { _fb_ds.local_addr<PT>(), Point(0, 0), _mode.area }; - Area const _size = _screen.size(); + Rect const _rect { { 0, 0 }, _screen.size() }; using Dirty_rect = Genode::Dirty_rect<Rect, 3>; @@ -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<Framebuffer_screen> _fb_screen { }; + bool _visible_at_fb_screen(Pointer pointer) const + { + return pointer.convert<bool>( + [&] (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<Main> _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<Main> _config_handler = { _env.ep(), *this, &Main::_handle_config }; + Signal_handler<Main> _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<Main> _focus_handler = { _env.ep(), *this, &Main::_handle_focus }; + Signal_handler<Main> _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 <util/xml_node.h> +#include <util/xml_generator.h> #include <util/color.h> #include <base/allocator.h> #include <gui_session/gui_session.h> @@ -39,6 +40,23 @@ namespace Nitpicker { { return Area(max(a1.w, a2.w), max(a1.h, a2.h)); } + + struct Nowhere { }; + + using Pointer = Attempt<Point, Nowhere>; + + 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<bool>( + [&] (Point const old) { + return _pointer.convert<bool>( + [&] (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<View_owner *>( + [&] (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<View_stack_elem> _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); } /**