input_filter: scroll-wheel emulation

The new '<button-scroll>' filter generates artificial wheel events from
relative motion events when the user holds a magic button.
This commit is contained in:
Norman Feske 2017-11-01 18:07:27 +01:00 committed by Christian Helmuth
parent 7b4ef66d91
commit a6b29530e8
6 changed files with 323 additions and 8 deletions

View File

@ -268,6 +268,44 @@ append config {
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
<message string="button-scroll feature"/>
<filter_config>
<input label="usb"/>
<output>
<button-scroll>
<input name="usb"/>
<vertical button="BTN_MIDDLE" speed_percent="-50"/>
<horizontal button="KEY_LEFTSHIFT" speed_percent="50"/>
</button-scroll>
</output>
</filter_config>
<sleep ms="100"/>
<usb>
<press code="BTN_MIDDLE"/> <release code="BTN_MIDDLE"/>
<motion rx="10" ry="10"/>
<press code="BTN_MIDDLE"/>
<motion rx="1" ry="1"/>
<motion rx="1" ry="1"/>
<press code="KEY_LEFTSHIFT"/>
<motion rx="1" ry="1"/>
<motion rx="1" ry="1"/>
<release code="KEY_LEFTSHIFT"/>
<release code="BTN_MIDDLE"/>
<motion rx="10" ry="10"/>
<press code="BTN_MIDDLE"/> <release code="BTN_MIDDLE"/>
</usb>
<!-- press-release w/o motion is reported at release time -->
<expect_press code="BTN_MIDDLE"/>
<expect_release code="BTN_MIDDLE"/>
<expect_motion rx="10" ry="10"/>
<expect_wheel rx="0" ry="-1"/>
<expect_wheel rx="1" ry="-1"/>
<expect_motion rx="10" ry="10"/>
<expect_press code="BTN_MIDDLE"/>
<expect_release code="BTN_MIDDLE"/>
<message string="survive deeply nested config"/>
<deep_filter_config depth="50"/>

View File

@ -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.
:<button-scroll>:
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 '<vertical>' and
'<horizontal>'. 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
-------------------------

View File

@ -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 <input/keycodes.h>
/* local includes */
#include <source.h>
#include <key_code_by_name.h>
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("<ignored/>");
}
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_ */

View File

@ -24,6 +24,7 @@
#include <remap_source.h>
#include <merge_source.h>
#include <chargen_source.h>
#include <button_scroll_source.h>
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();
}

View File

@ -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 { };
}

View File

@ -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: