diff --git a/repos/gems/src/app/floating_window_layouter/action.h b/repos/gems/src/app/floating_window_layouter/action.h new file mode 100644 index 000000000..127e508fa --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/action.h @@ -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 + static Type _type_by_string(String 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 + Action(String const &string) : _type(_type_by_string(string)) { } + + Type type() const { return _type; } +}; + +#endif /* _ACTION_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h b/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h new file mode 100644 index 000000000..b6fe5143d --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h @@ -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(""); + + 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 '' and '' nodes of the + * configuration according to the history of events of the current + * sequence. + * + * \return XML node of the type '' or ''. + * If the configuration does not contain a matching node, the + * method returns a dummy node ''. + */ + 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 + 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 + 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_ */ diff --git a/repos/gems/src/app/floating_window_layouter/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc index b6f0d36c3..b20d3da94 100644 --- a/repos/gems/src/app/floating_window_layouter/main.cc +++ b/repos/gems/src/app/floating_window_layouter/main.cc @@ -23,6 +23,7 @@ #include #include #include +#include /* 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.flush()); + input.flush(), Genode::config()->xml_node()); } Signal_dispatcher
input_dispatcher = { diff --git a/repos/gems/src/app/floating_window_layouter/user_state.h b/repos/gems/src/app/floating_window_layouter/user_state.h index eea1038ac..1bdcf68ef 100644 --- a/repos/gems/src/app/floating_window_layouter/user_state.h +++ b/repos/gems/src/app/floating_window_layouter/user_state.h @@ -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_ */