event_filter: new touch-key filter

The new <touch-key> filter type can be used to trigger artificial
press/release events for predefined touch-screen areas.

Fixes #4587
This commit is contained in:
Norman Feske 2022-08-22 15:46:02 +02:00 committed by Christian Helmuth
parent 59f1fe7625
commit 236ebecf44
6 changed files with 191 additions and 1 deletions

View File

@ -88,6 +88,7 @@ append config {
<driver name="usb"/>
<driver name="ps2"/>
<message string="test merging of two input sources"/>
<filter_config>
@ -483,6 +484,7 @@ append config {
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_A" char="b"/> <expect_release code="KEY_A"/>
<message string="test remap of KEY_UNKNOWN"/>
<filter_config>
@ -502,6 +504,7 @@ append config {
<expect_press code="KEY_A" />
<expect_release code="KEY_A" />
<message string="test ignore-key"/>
<filter_config>
@ -524,6 +527,7 @@ append config {
<not_expect_press code="KEY_A" />
<not_expect_release code="KEY_A" />
<message string="test log output"/>
<filter_config>
@ -545,6 +549,29 @@ append config {
<release code="KEY_UNKNOWN"/>
</usb>
<message string="test touch key"/>
<filter_config>
<output>
<touch-key>
<tap xpos="0" ypos="400" width="50" height="600" key="KEY_DASHBOARD"/>
<input name="usb"/>
</touch-key>
</output>
<policy label_suffix="usb" input="usb"/>
</filter_config>
<sleep ms="100"/>
<usb>
<touch x="10" y="500" /> <!-- hits special area -->
<touch-release/>
<touch x="100" y="500" /> <!-- besides special area -->
<touch-release/>
</usb>
<expect_press code="KEY_DASHBOARD" />
<expect_release code="KEY_DASHBOARD" />
<expect_touch x="100" y="500"/>
<expect_touch_release/>
</config>
<route>
<service name="Event"> <child name="event_filter"/> </service>

View File

@ -86,6 +86,14 @@ one of the following filters:
touch input. The original touch events are preserved, enabling touch-aware
applications to interpet touch gestures.
:<touch-key>:
Triggers an artificial key tap (a press event followed by a release event)
when touching a preconfigured area on a touch screen. The filter node can
host any number of '<tap>' sub nodes. Each sub node must define a
rectangular area - using the attributes 'xpos', 'ypos', 'width', and
'height' - and the name of the tapped key as 'key' attribute.
Character generator rules
-------------------------

View File

@ -26,6 +26,7 @@
#include <accelerate_source.h>
#include <log_source.h>
#include <touch_click_source.h>
#include <touch_key_source.h>
#include <event_session.h>
namespace Event_filter { struct Main; }
@ -254,6 +255,9 @@ struct Event_filter::Main : Source::Factory, Source::Trigger
if (node.type() == Touch_click_source::name())
return *new (_heap) Touch_click_source(owner, node, *this);
if (node.type() == Touch_key_source::name())
return *new (_heap) Touch_key_source(owner, node, *this, _heap);
warning("unknown <", node.type(), "> input-source node type");
throw Source::Invalid_config();
}

View File

@ -48,7 +48,8 @@ class Event_filter::Source
|| node.type() == "button-scroll"
|| node.type() == "accelerate"
|| node.type() == "log"
|| node.type() == "touch-click";
|| node.type() == "touch-click"
|| node.type() == "touch-key";
return false;
}

View File

@ -0,0 +1,128 @@
/*
* \brief Input-event source that generates press/release from touch events
* \author Norman Feske
* \date 2022-08-22
*
* This filter generates artificial key press/release event pairs when touching
* pre-defined areas on a touch screen. All events occurring while such a
* special area is touched are suppressed.
*/
/*
* Copyright (C) 2022 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 _EVENT_FILTER__TOUCH_KEY_SOURCE_H_
#define _EVENT_FILTER__TOUCH_KEY_SOURCE_H_
/* Genode includes */
#include <input/keycodes.h>
#include <util/geometry.h>
#include <base/allocator.h>
/* local includes */
#include <source.h>
namespace Event_filter { class Touch_key_source; }
class Event_filter::Touch_key_source : public Source, Source::Filter
{
private:
using Rect = Genode::Rect<>;
Owner _owner;
Source &_source;
Allocator &_alloc;
bool _pressed = false; /* true during touch sequence */
struct Tap : Interface
{
Rect const rect;
Input::Keycode const code;
static Input::Keycode code_from_xml(Xml_node const &node)
{
try {
return key_code_by_name(node.attribute_value("key", Key_name()));
}
catch (Unknown_key) { }
warning("ignoring tap rule ", node);
return Input::KEY_UNKNOWN;
}
Tap(Xml_node const &node)
: rect(Rect::from_xml(node)), code(code_from_xml(node)) { }
};
Registry<Registered<Tap>> _tap_rules { };
/**
* Filter interface
*/
void filter_event(Sink &destination, Input::Event const &event) override
{
Input::Event ev = event;
ev.handle_touch([&] (Input::Touch_id id, float x, float y) {
/* respond to initial touch of first finger only */
if (id.value != 0 || _pressed)
return;
_tap_rules.for_each([&] (Tap const &tap) {
if (tap.rect.contains(Point((int)(x), (int)(y)))) {
destination.submit(Input::Press { tap.code });
destination.submit(Input::Release { tap.code });
_pressed = true;
}
});
});
/* filter out all events during the touch sequence */
if (!_pressed)
destination.submit(ev);
ev.handle_touch_release([&] (Input::Touch_id id) {
if (id.value == 0)
_pressed = false;
});
}
public:
static char const *name() { return "touch-key"; }
Touch_key_source(Owner &owner, Xml_node config,
Source::Factory &factory, Allocator &alloc)
:
Source(owner),
_owner(factory),
_source(factory.create_source(_owner, input_sub_node(config))),
_alloc(alloc)
{
config.for_each_sub_node("tap", [&] (Xml_node const &node) {
new (_alloc) Registered<Tap>(_tap_rules, node); });
}
~Touch_key_source()
{
_tap_rules.for_each([&] (Registered<Tap> &tap) {
destroy(_alloc, &tap); });
}
void generate(Sink &destination) override
{
Source::Filter::apply(destination, *this, _source);
}
};
#endif /* _EVENT_FILTER__TOUCH_KEY_SOURCE_H_*/

View File

@ -259,6 +259,13 @@ class Test::Input_to_filter
if (motion && rel)
batch.submit(Input::Relative_motion{(int)node.attribute_value("rx", 0L),
(int)node.attribute_value("ry", 0L)});
if (node.has_type("touch"))
batch.submit(Input::Touch{ { 0 }, (float)node.attribute_value("x", 0.0),
(float)node.attribute_value("y", 0.0)});
if (node.has_type("touch-release"))
batch.submit(Input::Touch_release { { 0 } } );
});
});
}
@ -337,6 +344,8 @@ struct Test::Main : Input_from_filter::Event_handler
step.type() == "expect_release" ||
step.type() == "not_expect_press" ||
step.type() == "not_expect_release" ||
step.type() == "expect_touch" ||
step.type() == "expect_touch_release" ||
step.type() == "expect_char" ||
step.type() == "expect_motion" ||
step.type() == "expect_wheel");
@ -399,6 +408,7 @@ struct Test::Main : Input_from_filter::Event_handler
if (step.type() == "expect_press" || step.type() == "expect_release"
|| step.type() == "not_expect_press" || step.type() == "not_expect_release"
|| step.type() == "expect_touch" || step.type() == "expect_touch_release"
|| step.type() == "expect_char" || step.type() == "expect_motion"
|| step.type() == "expect_wheel")
return;
@ -485,6 +495,18 @@ struct Test::Main : Input_from_filter::Event_handler
&& (!step.has_attribute("ay") || step.attribute_value("ay", 0L) == y))
step_succeeded = true; });
ev.handle_touch([&] (Input::Touch_id, float x, float y) {
if (step.type() == "expect_touch"
&& ((float)step.attribute_value("x", 0.0) == x)
&& ((float)step.attribute_value("y", 0.0) == y))
step_succeeded = true;
});
ev.handle_touch_release([&] (Input::Touch_id) {
if (step.type() == "expect_touch_release")
step_succeeded = true;
});
if (step_failed) {
error("got unexpected event: ", step);
throw Exception();