window layouter: key sequence handling

This patch adds the mechanics for detecting key sequences to the window
layouter. Sequences for layouter actions can be expressed in the
layouter configuration. They cannot trigger any real action yet.
This commit is contained in:
Norman Feske 2016-02-02 19:33:06 +01:00
parent 6f27f85c3d
commit 2a916b143d
4 changed files with 357 additions and 5 deletions

View File

@ -0,0 +1,77 @@
/*
* \brief Action triggered by the user
* \author Norman Feske
* \date 2016-02-01
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _ACTION_H_
#define _ACTION_H_
namespace Floating_window_layouter { class Action; }
/**
* Result of the application of a key event to the key-sequence tracker
*/
class Floating_window_layouter::Action
{
public:
enum Type {
NONE,
NEXT_WINDOW,
PREV_WINDOW,
RAISE_WINDOW,
TOGGLE_FULLSCREEN,
CLOSE,
NEXT_WORKSPACE,
PREV_WORKSPACE,
MARK,
DETACH,
ATTACH,
COLUMN,
ROW,
REMOVE,
NEXT_COLUMN,
PREV_COLUMN,
NEXT_ROW,
PREV_ROW,
NEXT_TAB,
PREV_TAB,
TOOGLE_OVERLAY,
};
private:
Type _type;
template <Genode::size_t N>
static Type _type_by_string(String<N> const &string)
{
if (string == "next_window") return NEXT_WINDOW;
if (string == "prev_window") return PREV_WINDOW;
if (string == "raise_window") return RAISE_WINDOW;
if (string == "toggle_fullscreen") return TOGGLE_FULLSCREEN;
PWRN("cannot convert \"%s\" to action type", string.string());
return NONE;
}
public:
Action(Type type) : _type(type) { }
template <Genode::size_t N>
Action(String<N> const &string) : _type(_type_by_string(string)) { }
Type type() const { return _type; }
};
#endif /* _ACTION_H_ */

View File

@ -0,0 +1,250 @@
/*
* \brief Key seqyence tracker
* \author Norman Feske
* \date 2016-02-01
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _KEY_SEQUENCE_TRACKER_H_
#define _KEY_SEQUENCE_TRACKER_H_
/* local includes */
#include "action.h"
namespace Floating_window_layouter { class Key_sequence_tracker; }
class Floating_window_layouter::Key_sequence_tracker
{
private:
enum State {
/*
* No key is pressed. The next key will initiate a new key
* sequence.
*/
STATE_IDLE,
/*
* Key sequence has been started.
*/
STATE_IN_SEQUENCE,
};
State _state = STATE_IDLE;
struct Stack
{
struct Entry
{
enum Type { PRESS, RELEASE };
Type type = PRESS;
Input::Keycode keycode = Input::KEY_UNKNOWN;
Entry() { }
Entry(Type type, Input::Keycode keycode)
: type(type), keycode(keycode) { }
bool operator == (Entry const &other) const
{
return other.type == type && other.keycode == keycode;
}
};
/*
* Maximum number of consecutive press/release events in one key
* sequence.
*/
enum { MAX_ENTRIES = 64 };
Entry entries[MAX_ENTRIES];
unsigned pos = 0;
void push(Entry entry)
{
entries[pos++] = entry;
if (pos == MAX_ENTRIES) {
PWRN("Too long key sequence, dropping information");
pos = MAX_ENTRIES - 1;
}
}
/**
* Removes highest matching entry from stack
*/
void flush(Entry entry)
{
/* detect attempt to flush key from empty stack */
if (pos == 0)
return;
for (unsigned i = pos; i > 0; i--) {
if (entries[i - 1] == entry) {
/*
* Remove found entry by moving the subsequent entries
* by one position.
*/
for (unsigned j = i - 1; j < pos; j++)
entries[j] = entries[j + 1];
pos--;
return;
}
}
}
void reset() { pos = 0; }
};
Stack _stack;
Xml_node _matching_sub_node(Xml_node curr, Stack::Entry entry)
{
char const *node_type = entry.type == Stack::Entry::PRESS
? "press" : "release";
typedef Genode::String<32> Key_name;
Key_name const key(Input::key_name(entry.keycode));
Xml_node result("<none/>");
curr.for_each_sub_node(node_type, [&] (Xml_node const &node) {
if (node.attribute_value("key", Key_name()) != key)
return;
/* set 'result' only once, so we return the first match */
if (result.has_type("none"))
result = node;
});
return result;
}
/**
* Lookup XML node that matches the state of the key sequence
*
* Traverse the nested '<press>' and '<release>' nodes of the
* configuration according to the history of events of the current
* sequence.
*
* \return XML node of the type '<press>' or '<release>'.
* If the configuration does not contain a matching node, the
* method returns a dummy node '<none>'.
*/
Xml_node _xml_by_path(Xml_node config)
{
Xml_node curr = config;
/*
* Each iteration corresponds to a nesting level
*/
for (unsigned i = 0; i < _stack.pos; i++) {
Stack::Entry const entry = _stack.entries[i];
Xml_node const match = _matching_sub_node(curr, entry);
if (match.has_type("none"))
return match;
curr = match;
}
return curr;
}
/**
* Execute action denoted in the specific XML node
*/
template <typename FUNC>
void _execute_action(Xml_node node, FUNC const &func)
{
if (!node.has_attribute("action"))
return;
typedef String<32> Action;
Action action = node.attribute_value("action", Action());
func(Floating_window_layouter::Action(action));
}
public:
/**
* Start new key sequence
*/
void reset() { _stack.reset(); }
/**
* Apply event to key sequence
*
* \param func functor to be called if the event leads to a node in
* the key-sequence configuration and the node is
* equipped with an 'action' attribute. The functor is
* called with an 'Action' as argument.
*/
template <typename FUNC>
void apply(Input::Event const &ev, Xml_node config, FUNC const &func)
{
/*
* If the sequence contains a press-release combination for
* the pressed key, we flush those entries of the sequence
* to preserver the invariant that each key is present only
* once.
*/
if (ev.type() == Input::Event::PRESS) {
_stack.flush(Stack::Entry(Stack::Entry::PRESS, ev.keycode()));
_stack.flush(Stack::Entry(Stack::Entry::RELEASE, ev.keycode()));
}
Xml_node curr_node = _xml_by_path(config);
if (ev.type() == Input::Event::PRESS) {
Stack::Entry const entry(Stack::Entry::PRESS, ev.keycode());
_execute_action(_matching_sub_node(curr_node, entry), func);
_stack.push(entry);
}
if (ev.type() == Input::Event::RELEASE) {
Stack::Entry const entry(Stack::Entry::RELEASE, ev.keycode());
Xml_node const next_node = _matching_sub_node(curr_node, entry);
/*
* If there exists a specific path for the release event,
* follow the path. Otherwise, we remove the released key from
* the sequence.
*/
if (!next_node.has_type("none")) {
_execute_action(next_node, func);
_stack.push(entry);
} else {
Stack::Entry entry(Stack::Entry::PRESS, ev.keycode());
_stack.flush(entry);
}
}
}
};
#endif /* _KEY_SEQUENCE_TRACKER_H_ */

View File

@ -23,6 +23,7 @@
#include <input/keycodes.h>
#include <rom_session/connection.h>
#include <decorator/xml_utils.h>
#include <os/config.h>
/* local includes */
#include "window.h"
@ -248,7 +249,7 @@ struct Floating_window_layouter::Main : Operations
{
while (input.is_pending())
_user_state.handle_input(input_ds.local_addr<Input::Event>(),
input.flush());
input.flush(), Genode::config()->xml_node());
}
Signal_dispatcher<Main> input_dispatcher = {

View File

@ -16,6 +16,7 @@
/* local includes */
#include "operations.h"
#include "key_sequence_tracker.h"
namespace Floating_window_layouter { class User_state; }
@ -43,6 +44,8 @@ class Floating_window_layouter::User_state
unsigned _key_cnt = 0;
Key_sequence_tracker _key_sequence_tracker;
Window::Element _hovered_element = Window::Element::UNDEFINED;
Window::Element _dragged_element = Window::Element::UNDEFINED;
@ -70,7 +73,16 @@ class Floating_window_layouter::User_state
Operations &_operations;
inline void _handle_event(Input::Event const &);
bool _is_key(Input::Event const &ev) const
{
if (ev.type() != Input::Event::PRESS
&& ev.type() != Input::Event::RELEASE)
return false;
return ev.keycode() != Input::BTN_LEFT;
}
inline void _handle_event(Input::Event const &, Xml_node);
void _initiate_drag(Window_id hovered_window_id,
Window::Element hovered_element)
@ -119,12 +131,13 @@ class Floating_window_layouter::User_state
User_state(Operations &operations) : _operations(operations) { }
void handle_input(Input::Event const events[], unsigned num_events)
void handle_input(Input::Event const events[], unsigned num_events,
Xml_node const &config)
{
Point const pointer_last = _pointer_curr;
for (size_t i = 0; i < num_events; i++)
_handle_event(events[i]);
_handle_event(events[i], config);
/*
* Issue drag operation when in dragged state
@ -190,7 +203,8 @@ class Floating_window_layouter::User_state
};
void Floating_window_layouter::User_state::_handle_event(Input::Event const &e)
void Floating_window_layouter::User_state::_handle_event(Input::Event const &e,
Xml_node config)
{
if (e.type() == Input::Event::MOTION
|| e.type() == Input::Event::FOCUS) {
@ -261,6 +275,16 @@ void Floating_window_layouter::User_state::_handle_event(Input::Event const &e)
_operations.finalize_drag(_dragged_window_id, _dragged_element,
_pointer_clicked, _pointer_curr);
}
if (_is_key(e)) {
if (e.type() == Input::Event::PRESS && _key_cnt == 1)
_key_sequence_tracker.reset();
_key_sequence_tracker.apply(e, config, [&] (Action action) {
PINF("trigger action %d", action.type());
});
}
}
#endif /* _USER_STATE_H_ */