diff --git a/repos/os/run/input_filter.run b/repos/os/run/input_filter.run
index 1f38a15a0a..f9b6b8e400 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 f44f3affa9..f8d9814fe5 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 0000000000..5aad803a69
--- /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 1377bc6cb0..0734432255 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 3560d4d095..3f8bc009ca 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 24fd1e785c..ca8e4ce47f 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: