diff --git a/repos/os/run/input_filter.run b/repos/os/run/input_filter.run index 1f38a15a0..f9b6b8e40 100644 --- a/repos/os/run/input_filter.run +++ b/repos/os/run/input_filter.run @@ -268,6 +268,44 @@ append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/src/server/input_filter/README b/repos/os/src/server/input_filter/README index f44f3affa..f8d9814fe 100644 --- a/repos/os/src/server/input_filter/README +++ b/repos/os/src/server/input_filter/README @@ -37,6 +37,23 @@ one of the following filters: 'CHARACTER' events by applying character mapping rules. The originating filter is defined as a child node. +:: + + Turns relative motion events into wheel events while a special button + (i.e., the middle mouse button) is pressed. The button and rate of generated + wheel events can be configured per axis via the sub nodes '' and + ''. The button of each axis can be specified via the 'button' + attribute. By default, "BTN_MIDDLE" is used. The rate of generated wheel + events can be defined by the 'speed_percent' attribute. A value of "100" + uses relative motion vectors directly as wheel motion vectors. In practice, + this is results in overly fast wheel motion. By lowering the value, the rate + can be reduced to practical levels. By specifying a negative value, the + direction of the generated wheel motion can be inverted. + + The consumed relative motion events are filtered out from the event stream + such that pointer movements are inhibited while the wheel emulation is + active. All other events are passed along unmodified. + Character generator rules ------------------------- diff --git a/repos/os/src/server/input_filter/button_scroll_source.h b/repos/os/src/server/input_filter/button_scroll_source.h new file mode 100644 index 000000000..5aad803a6 --- /dev/null +++ b/repos/os/src/server/input_filter/button_scroll_source.h @@ -0,0 +1,231 @@ +/* + * \brief Input-event source that emulates a wheel from motion events + * \author Norman Feske + * \date 2017-11-01 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INPUT_FILTER__BUTTON_SCROLL_SOURCE_H_ +#define _INPUT_FILTER__BUTTON_SCROLL_SOURCE_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + +namespace Input_filter { class Button_scroll_source; } + + +class Input_filter::Button_scroll_source : public Source, Source::Sink +{ + private: + + struct Wheel + { + Input::Keycode const _button; + + static Key_name _button_attribute(Xml_node node) + { + return node.attribute_value("button", Key_name("BTN_MIDDLE")); + } + + /** + * Factor to scale motion events in percent + */ + int const _factor; + int const _factor_sign = (_factor < 0) ? -1 : 1; + int const _factor_percent = _factor_sign*_factor; + + /** + * True while user holds the configured button + */ + enum State { IDLE, BUTTON_PRESSED, ACTIVE }; + + State _state = IDLE; + + /** + * Sum of motion in current direction + */ + int _accumulated_motion = 0; + + bool _magic_button_press_event(Input::Event const &event) const + { + return (event.type() == Input::Event::PRESS) + && (event.keycode() == _button); + } + + bool _magic_button_release_event(Input::Event const &event) const + { + return (event.type() == Input::Event::RELEASE) + && (event.keycode() == _button); + } + + Wheel(Xml_node config) + : + _button(key_code_by_name(_button_attribute(config).string())), + _factor(config.attribute_value("speed_percent", 0L)) + { } + + void handle_activation(Input::Event const &event) + { + switch (_state) { + case IDLE: + if (_magic_button_press_event(event)) { + _state = BUTTON_PRESSED; + _accumulated_motion = 0; + } + break; + + case BUTTON_PRESSED: + if (event.relative_motion()) + _state = ACTIVE; + break; + + case ACTIVE: + break; + } + } + + /* + * \return true if press/release combination must be delivered + * + * If the release event follows the press event without + * intermediate motion, the press-release combination must be + * delivered at release time. + */ + bool handle_deactivation(Input::Event const &event) + { + if (_magic_button_release_event(event)) { + bool const emit_press_release = (_state == BUTTON_PRESSED); + _state = IDLE; + _accumulated_motion = 0; + return emit_press_release; + } + + return false; + } + + void apply_relative_motion(int motion) + { + if (_state != ACTIVE) return; + + /* reset if motion direction changes */ + if (motion*_accumulated_motion < 0) + _accumulated_motion = 0; + + _accumulated_motion += motion*_factor_percent; + } + + /** + * Return pending wheel motion + */ + int pending_motion() + { + int const quantizized = _accumulated_motion/100; + + if (quantizized != 0) + _accumulated_motion -= quantizized*100; + + return _factor_sign*quantizized; + } + + /** + * True if the given event must be filtered out from the event + * stream + */ + bool suppressed(Input::Event const event) + { + return (_state == ACTIVE && event.relative_motion()) + || _magic_button_press_event(event) + || _magic_button_release_event(event); + } + }; + + Wheel _vertical_wheel, _horizontal_wheel; + + Owner _owner; + + Source &_source; + + Source::Sink &_destination; + + /** + * Sink interface + */ + void submit_event(Input::Event const &event) override + { + using Input::Event; + + _vertical_wheel .handle_activation(event); + _horizontal_wheel.handle_activation(event); + + if (event.relative_motion()) { + _vertical_wheel .apply_relative_motion(event.ry()); + _horizontal_wheel.apply_relative_motion(event.rx()); + } + + /* emit artificial wheel event */ + int const wheel_x = _horizontal_wheel.pending_motion(), + wheel_y = _vertical_wheel .pending_motion(); + + if (wheel_x || wheel_y) + _destination.submit_event(Event(Event::WHEEL, 0, 0, 0, + wheel_x, wheel_y)); + + /* + * Submit both press event and release event of magic button at + * button-release time. + * + * Use bitwise or '|' instead of logical or '||' to always execute + * both conditions regardless of the result of the first call of + * 'handle_activation'. + */ + if (_vertical_wheel .handle_deactivation(event) + | _horizontal_wheel.handle_deactivation(event)) { + + _destination.submit_event(Event(Event::PRESS, event.code(), 0, 0, 0, 0)); + _destination.submit_event(event); + return; + } + + /* hide consumed relative motion and magic-button press events */ + if (_vertical_wheel .suppressed(event)) return; + if (_horizontal_wheel.suppressed(event)) return; + + /* forward unrelated events */ + _destination.submit_event(event); + } + + static Xml_node _sub_node(Xml_node node, char const *type) + { + return node.has_sub_node(type) ? node.sub_node(type) + : Xml_node(""); + } + + public: + + static char const *name() { return "button-scroll"; } + + Button_scroll_source(Owner &owner, Xml_node config, Source::Sink &destination, + Source::Factory &factory) + : + Source(owner), + _vertical_wheel (_sub_node(config, "vertical")), + _horizontal_wheel(_sub_node(config, "horizontal")), + _owner(factory), + _source(factory.create_source(_owner, input_sub_node(config), *this)), + _destination(destination) + { } + + void generate() override { _source.generate(); } +}; + +#endif /* _INPUT_FILTER__BUTTON_SCROLL_SOURCE_H_ */ diff --git a/repos/os/src/server/input_filter/main.cc b/repos/os/src/server/input_filter/main.cc index 1377bc6cb..073443225 100644 --- a/repos/os/src/server/input_filter/main.cc +++ b/repos/os/src/server/input_filter/main.cc @@ -24,6 +24,7 @@ #include #include #include +#include namespace Input_filter { struct Main; } @@ -193,7 +194,7 @@ struct Input_filter::Main : Input_connection::Avail_handler, /** * Maximum nesting depth of input sources, for limiting the stack usage */ - unsigned _create_source_max_nesting_level = 16; + unsigned _create_source_max_nesting_level = 12; /** * Source::Factory interface @@ -250,6 +251,9 @@ struct Input_filter::Main : Input_connection::Avail_handler, return *new (_heap) Chargen_source(owner, node, sink, *this, _heap, _timer_accessor, _include_accessor); + if (node.type() == Button_scroll_source::name()) + return *new (_heap) Button_scroll_source(owner, node, sink, *this); + warning("unknown <", node.type(), "> input-source node type"); throw Source::Invalid_config(); } diff --git a/repos/os/src/server/input_filter/source.h b/repos/os/src/server/input_filter/source.h index 3560d4d09..3f8bc009c 100644 --- a/repos/os/src/server/input_filter/source.h +++ b/repos/os/src/server/input_filter/source.h @@ -41,7 +41,8 @@ class Input_filter::Source return node.type() == "input" || node.type() == "remap" || node.type() == "chargen" - || node.type() == "merge"; + || node.type() == "merge" + || node.type() == "button-scroll"; return false; } @@ -57,7 +58,7 @@ class Input_filter::Source if (result.type() != "none") return result; - warning("missing input sub node in ", node); + warning("missing input-source sub node in ", node); throw Invalid_config { }; } diff --git a/repos/os/src/test/input_filter/main.cc b/repos/os/src/test/input_filter/main.cc index 24fd1e785..ca8e4ce47 100644 --- a/repos/os/src/test/input_filter/main.cc +++ b/repos/os/src/test/input_filter/main.cc @@ -212,14 +212,23 @@ class Test::Input_to_filter step.for_each_sub_node([&] (Xml_node node) { Input::Event::Type const type = - node.type() == "press" ? Input::Event::PRESS : + node.type() == "press" ? Input::Event::PRESS : node.type() == "release" ? Input::Event::RELEASE : + node.type() == "motion" ? Input::Event::MOTION : Input::Event::INVALID; if (type == Input::Event::PRESS || type == Input::Event::RELEASE) { Key_name const key_name = node.attribute_value("code", Key_name()); dst.submit(Input::Event(type, _code(key_name), 0, 0, 0, 0)); } + + if (type == Input::Event::MOTION) { + dst.submit(Input::Event(type, 0, + node.attribute_value("ax", 0L), + node.attribute_value("ay", 0L), + node.attribute_value("rx", 0L), + node.attribute_value("ry", 0L))); + } }); } }; @@ -294,7 +303,9 @@ struct Test::Main : Input_from_filter::Event_handler _input_from_filter.input_expected(step.type() == "expect_press" || step.type() == "expect_release" || - step.type() == "expect_char"); + step.type() == "expect_char" || + step.type() == "expect_motion" || + step.type() == "expect_wheel"); if (step.type() == "filter_config") { _publish_report(_input_filter_config_reporter, step); @@ -347,7 +358,8 @@ struct Test::Main : Input_from_filter::Event_handler } if (step.type() == "expect_press" || step.type() == "expect_release" - || step.type() == "expect_char") + || step.type() == "expect_char" || step.type() == "expect_motion" + || step.type() == "expect_wheel") return; if (step.type() == "sleep") { @@ -384,14 +396,26 @@ struct Test::Main : Input_from_filter::Event_handler && step.attribute_value("code", Value()) == Input::key_name(ev.keycode())) break; + case Input::Event::WHEEL: + if (step.type() == "expect_wheel" + && step.attribute_value("rx", 0L) == ev.rx() + && step.attribute_value("ry", 0L) == ev.ry()) + break; + + case Input::Event::MOTION: + if (step.type() == "expect_motion" + && (!step.has_attribute("rx") || step.attribute_value("rx", 0L) == ev.rx()) + && (!step.has_attribute("ry") || step.attribute_value("ry", 0L) == ev.ry()) + && (!step.has_attribute("ax") || step.attribute_value("ax", 0L) == ev.ax()) + && (!step.has_attribute("ay") || step.attribute_value("ay", 0L) == ev.ay())) + break; + case Input::Event::CHARACTER: if (step.type() == "expect_char" && step.attribute_value("char", Value()) == Value(Char(ev.utf8().b0))) break; case Input::Event::INVALID: - case Input::Event::MOTION: - case Input::Event::WHEEL: case Input::Event::FOCUS: case Input::Event::LEAVE: case Input::Event::TOUCH: