From f324aa902b382352f06f71c6e05938f13e44acf0 Mon Sep 17 00:00:00 2001 From: Norman Feske <norman.feske@genode-labs.com> Date: Fri, 27 Sep 2024 13:56:26 +0200 Subject: [PATCH] nitpicker: configurable capture policies Capture clients used to always capture the view stack at the origin of the coordinate system. So each capture client obtained a mirror of the same picture. This patch allows for the placement of capture clients on larger panorama using Genode's usual label-based policy-selection approach. Thereby, each monitor in multi-monitor scenario can display a different portion of the panorama. The patch takes special care to always keep the pointer in a visible position. The pointer cannot be moved to any area that is not captured. Should the only capture client displaying the pointer disappear, the pointer is warped to the center of (any) remaining capture client. Fixes #5352 --- repos/os/src/server/nitpicker/README | 41 ++++ .../os/src/server/nitpicker/capture_session.h | 102 ++++++++- .../os/src/server/nitpicker/domain_registry.h | 37 ++- repos/os/src/server/nitpicker/gui_session.cc | 8 +- repos/os/src/server/nitpicker/gui_session.h | 18 +- repos/os/src/server/nitpicker/main.cc | 211 +++++++++++++----- repos/os/src/server/nitpicker/types.h | 18 ++ repos/os/src/server/nitpicker/user_state.cc | 62 +++-- repos/os/src/server/nitpicker/user_state.h | 55 +++-- repos/os/src/server/nitpicker/view_stack.cc | 8 +- repos/os/src/server/nitpicker/view_stack.h | 14 +- 11 files changed, 418 insertions(+), 156 deletions(-) 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); } /**