nitpicker: Support for global keys

This commit is contained in:
Norman Feske 2013-09-06 17:34:16 +02:00
parent afbd7ea50e
commit 20daeb6d16
7 changed files with 403 additions and 81 deletions

View File

@ -102,6 +102,15 @@ append config {
<start name="nitpicker">
<resource name="RAM" quantum="1M"/>
<provides><service name="Nitpicker"/></provides>
<config>
<global-keys>
<key name="KEY_SCROLLLOCK" operation="xray" />
<key name="KEY_SYSRQ" operation="kill" />
<key name="KEY_PRINT" operation="kill" />
<key name="KEY_F11" operation="kill" />
<key name="KEY_F12" operation="xray" />
</global-keys>
</config>
</start>
<start name="launchpad">
<resource name="RAM" quantum="32M"/>

View File

@ -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.
! <config>
! <policy label="launchpad" color="#0000ff"/>
! <policy label="launchpad -> testnit" color="#ff0000"/>
! </config>
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.
! <config>
! <policy label="launchpad" color="#0000ff"/>
! <policy label="launchpad -> testnit" color="#ff0000"/>
! </config>
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:
! <config>
! <global-keys>
! <key name="KEY_SCROLLLOCK" operation="xray" />
! <key name="KEY_PRINT" operation="kill" />
! </global-keys>
! </config>
The '<global-keys>' node contains the policy for handling global keys. Each
'<key>' 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 '<key>' node. For the routing of global keys to
clients, the order of '<key>' nodes is important. If multiple nodes exists for
different labels, the first match will take effect. For example:
! <global-keys>
! <key name="KEY_F11" label="launchpad -> testnit" />
! <key name="KEY_F11" label="launchpad" />
! </global-keys>
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.

View File

@ -13,26 +13,21 @@
#include <input/event.h>
#include <input/keycodes.h>
#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);
}

View File

@ -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<PT> *cdt;
cdt = static_cast<Chunky_dataspace_texture<PT> *>(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_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_object<Input_handler,
typedef Pixel_rgb565 PT; /* physical pixel type */
/*****************************
** Handling of global keys **
*****************************/
Global_keys::Policy *Global_keys::_lookup_policy(char const *key_name)
{
for (unsigned i = 0; i < NUM_POLICIES; i++)
if (Genode::strcmp(key_name, Input::key_name((Input::Keycode)i)) == 0)
return &_policies[i];
return 0;
}
void Global_keys::apply_config(Session_list &session_list)
{
for (unsigned i = 0; i < NUM_POLICIES; i++)
_policies[i].undefine();
using Genode::Xml_node;
try {
Xml_node node = Genode::config()->xml_node().sub_node("global-keys").sub_node("key");
for (; ; node = node.next("key")) {
if (!node.has_attribute("name")) {
PWRN("attribute 'name' missing in <key> 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<PT> 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<PT> np_root(&ep, Area(mode.width(), mode.height()),
static Nitpicker::Root<PT> np_root(session_list, global_keys,
&ep, Area(mode.width(), mode.height()),
&user_state, &sliced_heap,
&screen, &framebuffer,
MENUBAR_HEIGHT);

View File

@ -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 <input/keycodes.h>
/* 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_ */

View File

@ -14,13 +14,21 @@
#ifndef _SESSION_H_
#define _SESSION_H_
/* Genode includes */
#include <util/list.h>
/* local includes */
#include "string.h"
class Texture;
class View;
class Session;
namespace Input { class Event; }
class Session
typedef Genode::List<Session> Session_list;
class Session : public Session_list::Element
{
public:

View File

@ -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