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