diff --git a/os/run/demo.run b/os/run/demo.run index 62ddd6e430..9d77626611 100644 --- a/os/run/demo.run +++ b/os/run/demo.run @@ -102,6 +102,15 @@ append config { + + + + + + + + + diff --git a/os/src/server/nitpicker/README b/os/src/server/nitpicker/README index 4c355b395a..7c1e5f9e0b 100644 --- a/os/src/server/nitpicker/README +++ b/os/src/server/nitpicker/README @@ -12,15 +12,56 @@ Configuration Nitpicker supports the following configuration options, supplied via Genode's config mechanism. -:Tinting of clients in X-Ray mode: +Tinting of clients in X-Ray mode +-------------------------------- - Nitpicker allows for assigning a color to single clients or a groups - of clients based on the client's label. The following configuration - tints all views of the launchpad subsystem in blue except for those - views that belong to the testnit child of launchpad. - ! - ! - ! - ! +Nitpicker allows for assigning a color to single clients or a groups +of clients based on the client's label. The following configuration +tints all views of the launchpad subsystem in blue except for those +views that belong to the testnit child of launchpad. +! +! +! +! + +Global key definitions +---------------------- + +Nitpicker has a few built-in function that can be activated via global +keyboard shortcuts, namely the X-ray mode and the kill mode. The keys +for toggling those functions can be defined as follows: + +! +! +! +! +! +! + +The '' node contains the policy for handling global keys. Each +'' subnode expresses a rule for named key. The 'operation' attribute +refers nitpicker's built-in operations. In the example above, the X-ray +mode can be activated via the scroll-lock key and the kill mode can be +activated via the print key. + +Alternatively to specifying an 'operation' attribute, a key node can contain +a 'label' attribute. If specified, all events regarding the key will be +reported to the client with the specified label. This enables clients to +handle global shortcuts. The client with the matching label will receive +all events until the number of concurrently pressed keys reaches zero. +This way, it is possible to handle chords of multiple keys starting with +the key specified in the '' node. For the routing of global keys to +clients, the order of '' nodes is important. If multiple nodes exists for +different labels, the first match will take effect. For example: + +! +! +! +! + +The "launchpad" client will receive all key sequences starting with F11 unless +the "launchpad -> testnit" program is running. As soon as testnit gets started +by launchpad, testnit will receive the events. If the order was reversed, +launchpad would always receive the events. diff --git a/os/src/server/nitpicker/common/user_state.cc b/os/src/server/nitpicker/common/user_state.cc index 691f595bcd..a8a9cb5ab8 100644 --- a/os/src/server/nitpicker/common/user_state.cc +++ b/os/src/server/nitpicker/common/user_state.cc @@ -13,26 +13,21 @@ #include #include + #include "user_state.h" using namespace Input; -/* - * Definition of magic keys - */ -enum { KILL_KEY = KEY_PRINT }; -enum { XRAY_KEY = KEY_SCROLLLOCK }; - /*************** ** Utilities ** ***************/ -static inline bool _masked_key(int keycode) { - return keycode == KILL_KEY || keycode == XRAY_KEY; } +static inline bool _masked_key(Global_keys &global_keys, Keycode keycode) { + return global_keys.is_kill_key(keycode) || global_keys.is_xray_key(keycode); } -static inline bool _mouse_button(int keycode) { +static inline bool _mouse_button(Keycode keycode) { return keycode >= BTN_LEFT && keycode <= BTN_MIDDLE; } @@ -40,27 +35,27 @@ static inline bool _mouse_button(int keycode) { ** User state interface ** **************************/ -User_state::User_state(Canvas *canvas, Menubar *menubar): - View_stack(canvas, this), _key_cnt(0), _menubar(menubar), - _pointed_view(0) { } +User_state::User_state(Global_keys &global_keys, Canvas *canvas, Menubar *menubar) +: + View_stack(canvas, this), _global_keys(global_keys), _key_cnt(0), + _menubar(menubar), _pointed_view(0), _input_receiver(0), + _global_key_sequence(false) +{ } void User_state::handle_event(Input::Event ev) { + Input::Keycode const keycode = ev.keycode(); + Input::Event::Type const type = ev.type(); + /* * Mangle incoming events */ - - int keycode = ev.code(); int ax = _mouse_pos.x(), ay = _mouse_pos.y(); int rx = 0, ry = 0; /* skip info about relative motion per default */ - /* KEY_PRINT and KEY_SYSRQ both enter kill mode */ - if ((ev.type() == Event::PRESS) && (ev.code() == KEY_SYSRQ)) - keycode = KEY_PRINT; - /* transparently handle absolute and relative motion events */ - if (ev.type() == Event::MOTION) { + if (type == Event::MOTION) { if ((ev.rx() || ev.ry()) && ev.ax() == 0 && ev.ay() == 0) { ax = max(0, min(size().w(), ax + ev.rx())); ay = max(0, min(size().h(), ay + ev.ry())); @@ -71,17 +66,17 @@ void User_state::handle_event(Input::Event ev) } /* propagate relative motion for wheel events */ - if (ev.type() == Event::WHEEL) { + if (type == Event::WHEEL) { rx = ev.rx(); ry = ev.ry(); } /* create the mangled event */ - ev = Input::Event(ev.type(), keycode, ax, ay, rx, ry); + ev = Input::Event(type, keycode, ax, ay, rx, ry); _mouse_pos = Point(ax, ay); - View *pointed_view = find_view(_mouse_pos); + View * pointed_view = find_view(_mouse_pos); /* * Deliver a leave event if pointed-to session changed @@ -95,32 +90,54 @@ void User_state::handle_event(Input::Event ev) /* remember currently pointed-at view */ _pointed_view = pointed_view; - /* - * We expect pointed view to be always defined. In the worst case (with no - * view at all), the pointed view is the background. + /** + * Guard that performs a whole-screen update when leaving the scope */ + struct Update_all_guard + { + User_state &user_state; + bool enabled; + char const *menu_title; - bool update_all = false; + Update_all_guard(User_state &user_state) + : user_state(user_state), enabled(false), menu_title("") { } + + ~Update_all_guard() + { + if (!enabled) + return; + + if (user_state._input_receiver) + user_state._menubar->state(user_state, user_state._input_receiver->label(), + menu_title, user_state._input_receiver->color()); + else + user_state._menubar->state(user_state, "", "", BLACK); + + user_state.update_all_views(); + } + } update_all_guard(*this); /* * Detect mouse press event in kill mode, used to select the session * to lock out. */ - if (kill() && ev.type() == Event::PRESS && ev.code() == Input::BTN_LEFT) { + if (kill() && type == Event::PRESS && keycode == Input::BTN_LEFT) { if (pointed_view && pointed_view->session()) lock_out_session(pointed_view->session()); /* leave kill mode */ - pointed_view = 0; - Mode::_mode &= ~KILL; - update_all = true; + pointed_view = 0; + Mode::_mode &= ~KILL; + update_all_guard.enabled = true; } - if (ev.type() == Event::PRESS && _key_cnt == 0) { + /* + * Handle start of a key sequence + */ + if (type == Event::PRESS && _key_cnt == 0) { /* update focused view */ - if (pointed_view != focused_view() - && _mouse_button(ev.code())) { + if (pointed_view != focused_view() && _mouse_button(keycode)) { bool const focus_stays_in_session = (_focused_view && pointed_view && @@ -131,7 +148,7 @@ void User_state::handle_event(Input::Event ev) * changing the focus to another session. */ if (flat() && !focus_stays_in_session) - update_all = true; + update_all_guard.enabled = true; /* * Notify both the old focussed session and the new one. @@ -150,43 +167,63 @@ void User_state::handle_event(Input::Event ev) } if (!flat() || !_focused_view || !pointed_view) - update_all = true; + update_all_guard.enabled = true; _focused_view = pointed_view; } - /* toggle kill and xray modes */ - if (ev.code() == KILL_KEY || ev.code() == XRAY_KEY) { + /* + * If there exists a global rule for the pressed key, set the + * corresponding session as receiver of the input stream until the key + * count reaches zero. Otherwise, the input stream is directed to the + * pointed-at view. + * + * If we deliver a global key sequence, we temporarily change the focus + * to the global receiver. To reflect that change, we need to update + * the whole screen. + */ + Session * const global_receiver = _global_keys.global_receiver(keycode); + if (global_receiver) { + _global_key_sequence = true; + _input_receiver = global_receiver; + update_all_guard.menu_title = ""; + update_all_guard.enabled = true; + } - Mode::_mode ^= ev.code() == KILL_KEY ? KILL : XRAY; - update_all = true; + /* + * No global rule matched, so the input stream gets directed to the + * focused view or refers to a built-in operation. + */ + if (!global_receiver && _focused_view) { + _input_receiver = _focused_view->session(); + update_all_guard.menu_title = _focused_view->title(); + } + + /* + * Toggle kill and xray modes. If one of those keys is pressed, + * suppress the delivery to clients. + */ + if (_global_keys.is_operation_key(keycode)) { + + Mode::_mode ^= _global_keys.is_kill_key(keycode) ? KILL : 0 + | _global_keys.is_xray_key(keycode) ? XRAY : 0; + + update_all_guard.enabled = true; + _input_receiver = 0; } } - if (update_all) { - - if (focused_view() && focused_view()->session()) - _menubar->state(*this, focused_view()->session()->label(), - focused_view()->title(), - focused_view()->session()->color()); - else - _menubar->state(*this, "", "", BLACK); - - update_all_views(); - } - /* count keys */ - if (ev.type() == Event::PRESS) _key_cnt++; - if (ev.type() == Event::RELEASE && _key_cnt > 0) _key_cnt--; + if (type == Event::PRESS) _key_cnt++; + if (type == Event::RELEASE && _key_cnt > 0) _key_cnt--; /* - * Deliver event to Nitpicker session. - * (except when kill mode is activated) + * Deliver event to Nitpicker session except when kill mode is activated */ if (kill()) return; - if (ev.type() == Event::MOTION || ev.type() == Event::WHEEL) { + if (type == Event::MOTION || type == Event::WHEEL) { if (_key_cnt == 0) { @@ -199,14 +236,24 @@ void User_state::handle_event(Input::Event ev) if (pointed_view) pointed_view->session()->submit_input_event(&ev); - } else if (focused_view()) - focused_view()->session()->submit_input_event(&ev); + } else if (_input_receiver) + _input_receiver->submit_input_event(&ev); } /* deliver press/release event to session with focused view */ - if (ev.type() == Event::PRESS || ev.type() == Event::RELEASE) - if (!_masked_key(ev.code()) && focused_view()) - focused_view()->session()->submit_input_event(&ev); + if (type == Event::PRESS || type == Event::RELEASE) + if (_input_receiver) + _input_receiver->submit_input_event(&ev); + + /* + * Detect end of global key sequence + */ + if (ev.type() == Event::RELEASE && _key_cnt == 0 && _global_key_sequence) { + _input_receiver = _focused_view ? _focused_view->session() : 0; + update_all_guard.menu_title = _focused_view ? _focused_view->title() : ""; + update_all_guard.enabled = true; + _global_key_sequence = false; + } } @@ -221,6 +268,8 @@ void User_state::forget(View *v) _menubar->state(*this, "", "", BLACK); update_all_views(); } + if (_input_receiver == v->session()) + _input_receiver = 0; if (_pointed_view == v) _pointed_view = find_view(_mouse_pos); } diff --git a/os/src/server/nitpicker/genode/main.cc b/os/src/server/nitpicker/genode/main.cc index e566b514ff..6e84878e39 100644 --- a/os/src/server/nitpicker/genode/main.cc +++ b/os/src/server/nitpicker/genode/main.cc @@ -553,6 +553,8 @@ namespace Nitpicker { { private: + Session_list &_session_list; + Global_keys &_global_keys; Area _scr_size; View_stack *_view_stack; Flush_merger *_flush_merger; @@ -595,12 +597,17 @@ namespace Nitpicker { bool provides_default_bg = (Genode::strcmp(label_buf, "backdrop") == 0); - return new (md_alloc()) - Session_component(label_buf, cdt, cdt, _view_stack, ep(), - _flush_merger, _framebuffer, v_offset, - cdt->input_mask_buffer(), - provides_default_bg, session_color(args), - stay_top); + Session_component *session = new (md_alloc()) + Session_component(label_buf, cdt, cdt, _view_stack, ep(), + _flush_merger, _framebuffer, v_offset, + cdt->input_mask_buffer(), + provides_default_bg, session_color(args), + stay_top); + + _session_list.insert(session); + _global_keys.apply_config(_session_list); + + return session; } void _destroy_session(Session_component *session) @@ -608,6 +615,9 @@ namespace Nitpicker { Chunky_dataspace_texture *cdt; cdt = static_cast *>(session->texture()); + _session_list.remove(session); + _global_keys.apply_config(_session_list); + destroy(md_alloc(), session); destroy(md_alloc(), cdt); } @@ -617,12 +627,14 @@ namespace Nitpicker { /** * Constructor */ - Root(Genode::Rpc_entrypoint *session_ep, Area scr_size, + Root(Session_list &session_list, Global_keys &global_keys, + Genode::Rpc_entrypoint *session_ep, Area scr_size, View_stack *view_stack, Genode::Allocator *md_alloc, Flush_merger *flush_merger, Framebuffer::Session *framebuffer, int default_v_offset) : Genode::Root_component(session_ep, md_alloc), + _session_list(session_list), _global_keys(global_keys), _scr_size(scr_size), _view_stack(view_stack), _flush_merger(flush_merger), _framebuffer(framebuffer), _default_v_offset(default_v_offset) { } }; @@ -789,6 +801,85 @@ class Input_handler_component : public Genode::Rpc_objectxml_node().sub_node("global-keys").sub_node("key"); + + for (; ; node = node.next("key")) { + + if (!node.has_attribute("name")) { + PWRN("attribute 'name' missing in config node"); + continue; + } + + char name[32]; name[0] = 0; + node.attribute("name").value(name, sizeof(name)); + + Policy * policy = _lookup_policy(name); + if (!policy) { + PWRN("invalid key name \"%s\"", name); + continue; + } + + /* if two policies match, give precedence to policy defined first */ + if (policy->defined()) + continue; + + if (node.has_attribute("operation")) { + Xml_node::Attribute operation = node.attribute("operation"); + + if (operation.has_value("kill")) { + policy->operation_kill(); + continue; + } else if (operation.has_value("xray")) { + policy->operation_xray(); + continue; + } else { + char buf[32]; buf[0] = 0; + operation.value(buf, sizeof(buf)); + PWRN("unknown operation \"%s\" for key %s", buf, name); + } + continue; + } + + if (!node.has_attribute("label")) { + PWRN("missing 'label' attribute for key %s", name); + continue; + } + + /* assign policy to matching client session */ + for (Session *s = session_list.first(); s; s = s->next()) + if (node.attribute("label").has_value(s->label())) + policy->client(s); + } + + } catch (Xml_node::Nonexistent_sub_node) { } +} + + +/****************** + ** Main program ** + ******************/ + int main(int argc, char **argv) { using namespace Genode; @@ -828,7 +919,18 @@ int main(int argc, char **argv) PT *menubar_pixels = (PT *)env()->heap()->alloc(sizeof(PT)*mode.width()*16); Chunky_menubar menubar(menubar_pixels, Area(mode.width(), MENUBAR_HEIGHT)); - User_state user_state(&screen, &menubar); + static Global_keys global_keys; + + static Session_list session_list; + + /* + * Apply initial global-key policy to turn X-ray and kill keys into + * effect. The policy will be updated each time the config changes, + * or when a session appears or disappears. + */ + global_keys.apply_config(session_list); + + static User_state user_state(global_keys, &screen, &menubar); /* * Create view stack with default elements @@ -852,7 +954,8 @@ int main(int argc, char **argv) Sliced_heap sliced_heap(env()->ram_session(), env()->rm_session()); - static Nitpicker::Root np_root(&ep, Area(mode.width(), mode.height()), + static Nitpicker::Root np_root(session_list, global_keys, + &ep, Area(mode.width(), mode.height()), &user_state, &sliced_heap, &screen, &framebuffer, MENUBAR_HEIGHT); diff --git a/os/src/server/nitpicker/include/global_keys.h b/os/src/server/nitpicker/include/global_keys.h new file mode 100644 index 0000000000..038e73d7d9 --- /dev/null +++ b/os/src/server/nitpicker/include/global_keys.h @@ -0,0 +1,96 @@ +/* + * \brief Global keys policy + * \author Norman Feske + * \date 2013-09-06 + */ + +/* + * Copyright (C) 2013 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 _GLOBAL_KEYS_H_ +#define _GLOBAL_KEYS_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include "session.h" + +class Global_keys +{ + private: + + struct Policy + { + enum Type { + + /** + * Key is not global but should be propagated to focused client + */ + UNDEFINED, + + /** + * Key activates nitpicker's built-in kill mode + */ + KILL, + + /** + * Key activates nitpicker's built-in X-ray mode + */ + XRAY, + + /** + * Key should be propagated to client session + */ + CLIENT + }; + + Type _type; + Session *_session; + + Policy() : _type(UNDEFINED), _session(0) { } + + void undefine() { _type = UNDEFINED; _session = 0; } + void operation_kill() { _type = KILL; _session = 0; } + void operation_xray() { _type = XRAY; _session = 0; } + void client(Session *s) { _type = CLIENT; _session = s; } + + bool defined() const { return _type != UNDEFINED; } + bool xray() const { return _type == XRAY; } + bool kill() const { return _type == KILL; } + }; + + enum { NUM_POLICIES = Input::KEY_MAX + 1 }; + + Policy _policies[NUM_POLICIES]; + + /** + * Lookup policy that matches the specified key name + */ + Policy *_lookup_policy(char const *key_name); + + bool _valid(Input::Keycode key) const { + return key >= 0 && key <= Input::KEY_MAX; } + + public: + + Session *global_receiver(Input::Keycode key) { + return _valid(key) ? _policies[key]._session : 0; } + + void apply_config(Session_list &session_list); + + bool is_operation_key(Input::Keycode key) const { + return _valid(key) && (_policies[key].xray() || _policies[key].kill()); } + + bool is_xray_key(Input::Keycode key) const { + return _valid(key) && _policies[key].xray(); } + + bool is_kill_key(Input::Keycode key) const { + return _valid(key) && _policies[key].kill(); } +}; + +#endif /* _GLOBAL_KEYS_H_ */ diff --git a/os/src/server/nitpicker/include/session.h b/os/src/server/nitpicker/include/session.h index 6c525e6ca4..906dd6d20c 100644 --- a/os/src/server/nitpicker/include/session.h +++ b/os/src/server/nitpicker/include/session.h @@ -14,13 +14,21 @@ #ifndef _SESSION_H_ #define _SESSION_H_ +/* Genode includes */ +#include + +/* local includes */ #include "string.h" class Texture; class View; +class Session; + namespace Input { class Event; } -class Session +typedef Genode::List Session_list; + +class Session : public Session_list::Element { public: diff --git a/os/src/server/nitpicker/include/user_state.h b/os/src/server/nitpicker/include/user_state.h index 9254f1ac3b..729969f147 100644 --- a/os/src/server/nitpicker/include/user_state.h +++ b/os/src/server/nitpicker/include/user_state.h @@ -23,11 +23,17 @@ #include "mode.h" #include "menubar.h" #include "view_stack.h" +#include "global_keys.h" class User_state : public Mode, public View_stack { private: + /* + * Policy for the routing of global keys + */ + Global_keys &_global_keys; + /* * Number of currently pressed keys. * This counter is used to determine if the user @@ -52,12 +58,22 @@ class User_state : public Mode, public View_stack */ View *_pointed_view; + /* + * Session that receives the current stream of input events + */ + Session *_input_receiver; + + /* + * True while a global key sequence is processed + */ + bool _global_key_sequence; + public: /** * Constructor */ - User_state(Canvas *canvas, Menubar *menubar); + User_state(Global_keys &global_keys, Canvas *canvas, Menubar *menubar); /** * Handle input event