mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 11:55:24 +00:00
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:
parent
7b4ef66d91
commit
a6b29530e8
@ -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"/>
|
||||
|
@ -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
|
||||
-------------------------
|
||||
|
231
repos/os/src/server/input_filter/button_scroll_source.h
Normal file
231
repos/os/src/server/input_filter/button_scroll_source.h
Normal 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_ */
|
@ -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();
|
||||
}
|
||||
|
@ -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 { };
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user