genode/repos/os/src/server/nitpicker/user_state.cc

459 lines
12 KiB
C++
Raw Normal View History

2011-12-22 16:19:25 +01:00
/*
* \brief User state implementation
* \author Norman Feske
* \date 2006-08-27
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
2011-12-22 16:19:25 +01:00
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
2011-12-22 16:19:25 +01:00
*/
#include <input/event.h>
#include <input/keycodes.h>
2013-09-06 17:34:16 +02:00
2011-12-22 16:19:25 +01:00
#include "user_state.h"
using namespace Input;
/***************
** Utilities **
***************/
2013-09-06 17:34:16 +02:00
static inline bool _mouse_button(Keycode keycode) {
2011-12-22 16:19:25 +01:00
return keycode >= BTN_LEFT && keycode <= BTN_MIDDLE; }
/**
* Determine number of events that can be merged into one
*
* \param ev pointer to first event array element to check
* \param max size of the event array
* \return number of events subjected to merge
*/
static unsigned num_consecutive_events(Input::Event const *ev, unsigned max)
{
if (max < 1) return 0;
if (ev->type() != Input::Event::MOTION) return 1;
2011-12-22 16:19:25 +01:00
bool const first_absolute = ev->absolute_motion();
2011-12-22 16:19:25 +01:00
/* iterate until we get a different event type, start at second */
unsigned cnt = 1;
for (ev++ ; cnt < max; cnt++, ev++) {
if (ev->type() != Input::Event::MOTION) break;
if (first_absolute != ev->absolute_motion()) break;
}
return cnt;
}
2011-12-22 16:19:25 +01:00
/**
* Merge consecutive motion events
*
* \param ev event array to merge
* \param n number of events to merge
* \return merged motion event
*/
static Input::Event merge_motion_events(Input::Event const *ev, unsigned n)
{
Input::Event res;
for (unsigned i = 0; i < n; i++, ev++)
res = Input::Event(Input::Event::MOTION, 0, ev->ax(), ev->ay(),
res.rx() + ev->rx(), res.ry() + ev->ry());
return res;
}
/**************************
** User state interface **
**************************/
void User_state::_handle_input_event(Input::Event ev)
2011-12-22 16:19:25 +01:00
{
2013-09-06 17:34:16 +02:00
Input::Keycode const keycode = ev.keycode();
Input::Event::Type const type = ev.type();
2011-12-22 16:19:25 +01:00
/*
* Mangle incoming events
*/
int ax = _pointer_pos.x(), ay = _pointer_pos.y();
2013-09-07 02:02:26 +02:00
int rx = 0, ry = 0; /* skip info about relative motion by default */
2011-12-22 16:19:25 +01:00
/* transparently handle absolute and relative motion events */
2013-09-06 17:34:16 +02:00
if (type == Event::MOTION) {
2011-12-22 16:19:25 +01:00
if ((ev.rx() || ev.ry()) && ev.ax() == 0 && ev.ay() == 0) {
ax = max(0, min((int)_view_stack.size().w() - 1, ax + ev.rx()));
ay = max(0, min((int)_view_stack.size().h() - 1, ay + ev.ry()));
2011-12-22 16:19:25 +01:00
} else {
ax = ev.ax();
ay = ev.ay();
}
}
/* propagate relative motion for wheel events */
2013-09-06 17:34:16 +02:00
if (type == Event::WHEEL) {
2011-12-22 16:19:25 +01:00
rx = ev.rx();
ry = ev.ry();
}
if (type == Event::TOUCH) {
ax = ev.ax();
ay = ev.ay();
ev = Input::Event::create_touch_event(ax, ay, ev.code(),
ev.touch_release());
} else if (type == Event::CHARACTER) {
ev = Input::Event(type, ev.code(), ax, ay, rx, ry);
} else
ev = Input::Event(type, keycode, ax, ay, rx, ry);
2011-12-22 16:19:25 +01:00
_pointer_pos = Point(ax, ay);
2011-12-22 16:19:25 +01:00
bool const drag = _key_cnt > 0;
2013-09-07 02:02:26 +02:00
/* count keys */
if (type == Event::PRESS) _key_cnt++;
if (type == Event::RELEASE && drag) _key_cnt--;
2013-09-07 02:02:26 +02:00
/* track key states */
if (type == Event::PRESS) {
if (_key_array.pressed(keycode))
Genode::warning("suspicious double press of ", Input::key_name(keycode));
_key_array.pressed(keycode, true);
}
if (type == Event::RELEASE) {
if (!_key_array.pressed(keycode))
Genode::warning("suspicious double release of ", Input::key_name(keycode));
_key_array.pressed(keycode, false);
}
View_component const * const pointed_view = _view_stack.find_view(_pointer_pos);
View_owner * const hovered = pointed_view ? &pointed_view->owner() : 0;
2011-12-22 16:19:25 +01:00
/*
* Deliver a leave event if pointed-to session changed
*/
if (_hovered && (hovered != _hovered)) {
2013-09-07 02:02:26 +02:00
2011-12-22 16:19:25 +01:00
Input::Event leave_ev(Input::Event::LEAVE, 0, ax, ay, 0, 0);
_hovered->submit_input_event(leave_ev);
2011-12-22 16:19:25 +01:00
}
_hovered = hovered;
2011-12-22 16:19:25 +01:00
2013-09-06 17:34:16 +02:00
/*
* Handle start of a key sequence
*/
if (type == Event::PRESS && (_key_cnt == 1)) {
2013-09-07 02:02:26 +02:00
View_owner *global_receiver = nullptr;
2011-12-22 16:19:25 +01:00
/* update focused session */
if (_mouse_button(keycode)
&& _hovered
&& (_hovered != _focused)
&& (_hovered->has_focusable_domain()
|| _hovered->has_same_domain(_focused))) {
2011-12-22 16:19:25 +01:00
/*
* Notify both the old focused session and the new one.
2011-12-22 16:19:25 +01:00
*/
if (_focused) {
Input::Event unfocus_ev(Input::Event::FOCUS, 0, ax, ay, 0, 0);
_focused->submit_input_event(unfocus_ev);
}
2011-12-22 16:19:25 +01:00
if (_hovered) {
Input::Event focus_ev(Input::Event::FOCUS, 1, ax, ay, 0, 0);
_hovered->submit_input_event(focus_ev);
}
if (_hovered->has_transient_focusable_domain()) {
global_receiver = _hovered;
} else {
/*
* Distinguish the use of the builtin focus switching and the
* alternative use of an external focus policy. In the latter
* case, focusable domains are handled like transiently
* focusable domains. The permanent focus change is triggered
* by an external focus-policy component by posting and updated
* focus ROM, which is then propagated into the user state via
* the 'User_state::focus' and 'User_state::reset_focus'
* methods.
*/
if (_focus_via_click)
_focus_view_owner_via_click(*_hovered);
else
global_receiver = _hovered;
_last_clicked = _hovered;
}
2011-12-22 16:19:25 +01:00
}
2013-09-06 17:34:16 +02:00
/*
* 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 session.
2013-09-06 17:34:16 +02:00
*
* 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.
*/
if (!global_receiver)
global_receiver = _global_keys.global_receiver(keycode);
2013-09-06 17:34:16 +02:00
if (global_receiver) {
_global_key_sequence = true;
_input_receiver = global_receiver;
2013-09-06 17:34:16 +02:00
}
2011-12-22 16:19:25 +01:00
2013-09-06 17:34:16 +02:00
/*
* No global rule matched, so the input stream gets directed to the
* focused session or refers to a built-in operation.
2013-09-06 17:34:16 +02:00
*/
if (!global_receiver)
_input_receiver = _focused;
2011-12-22 16:19:25 +01:00
}
/*
* Deliver event to session
2011-12-22 16:19:25 +01:00
*/
if (type == Event::MOTION || type == Event::WHEEL || type == Event::TOUCH) {
2011-12-22 16:19:25 +01:00
if (_key_cnt == 0) {
2011-12-22 16:19:25 +01:00
if (_hovered) {
/*
* Unless the domain of the pointed session is configured to
* always receive hover events, we deliver motion events only
* to the focused domain.
*/
if (_hovered->hover_always()
|| _hovered->has_same_domain(_focused))
_hovered->submit_input_event(ev);
}
2011-12-22 16:19:25 +01:00
2013-09-06 17:34:16 +02:00
} else if (_input_receiver)
2013-09-07 02:02:26 +02:00
_input_receiver->submit_input_event(ev);
2011-12-22 16:19:25 +01:00
}
/*
* Deliver press/release event to focused session or the receiver of global
* key.
*/
if ((type == Event::PRESS) && _input_receiver) {
if (!_mouse_button(ev.keycode())
|| (_hovered
&& (_hovered->has_focusable_domain()
|| _hovered->has_same_domain(_focused))))
2013-09-07 02:02:26 +02:00
_input_receiver->submit_input_event(ev);
else
_input_receiver = nullptr;
}
if ((type == Event::RELEASE) && _input_receiver)
_input_receiver->submit_input_event(ev);
2013-09-06 17:34:16 +02:00
/*
* Forward character events
*/
if (type == Event::CHARACTER && _input_receiver)
_input_receiver->submit_input_event(ev);
2013-09-06 17:34:16 +02:00
/*
* Detect end of global key sequence
*/
if (ev.type() == Event::RELEASE && (_key_cnt == 0) && _global_key_sequence) {
_input_receiver = _focused;
_global_key_sequence = false;
}
}
User_state::Handle_input_result
User_state::handle_input_events(Input::Event const * const ev_buf,
unsigned const num_ev)
{
Point const old_pointer_pos = _pointer_pos;
View_owner * const old_hovered = _hovered;
View_owner const * const old_focused = _focused;
View_owner const * const old_input_receiver = _input_receiver;
View_owner const * const old_last_clicked = _last_clicked;
bool user_active = false;
if (num_ev > 0) {
/*
* Take events from input event buffer, merge consecutive motion
* events, and pass result to the user state.
*/
for (unsigned src_ev_cnt = 0; src_ev_cnt < num_ev; src_ev_cnt++) {
Input::Event const *e = &ev_buf[src_ev_cnt];
Input::Event curr = *e;
if (e->type() == Input::Event::MOTION) {
unsigned n = num_consecutive_events(e, num_ev - src_ev_cnt);
curr = merge_motion_events(e, n);
/* skip merged events */
src_ev_cnt += n - 1;
}
/*
* If subsequential relative motion events are merged to
* a zero-motion event, drop it. Otherwise, it would be
* misinterpreted as absolute event pointing to (0, 0).
*/
if (e->relative_motion() && curr.rx() == 0 && curr.ry() == 0)
continue;
/*
* If we detect a pressed key sometime during the event processing,
* we regard the user as active. This check captures the presence
* of press-release combinations within one batch of input events.
*/
user_active |= _key_pressed();
/* pass event to user state */
_handle_input_event(curr);
}
} else {
/*
* Besides handling input events, 'user_state.handle_event()' also
* updates the pointed session, which might have changed by other
* means, for example view movement.
*/
_handle_input_event(Input::Event());
2013-09-06 17:34:16 +02:00
}
/*
* If at least one key is kept pressed, we regard the user as active.
*/
user_active |= _key_pressed();
bool key_state_affected = false;
for (unsigned i = 0; i < num_ev; i++)
key_state_affected |= (ev_buf[i].type() == Input::Event::PRESS) ||
(ev_buf[i].type() == Input::Event::RELEASE);
_apply_pending_focus_change();
return {
.pointer_position_changed = _pointer_pos != old_pointer_pos,
.hover_changed = _hovered != old_hovered,
.focus_changed = (_focused != old_focused) ||
(_input_receiver != old_input_receiver),
.key_state_affected = key_state_affected,
.user_active = user_active,
.key_pressed = _key_pressed(),
.last_clicked_changed = (_last_clicked != old_last_clicked)
};
2011-12-22 16:19:25 +01:00
}
void User_state::report_keystate(Xml_generator &xml) const
{
xml.attribute("count", _key_cnt);
_key_array.report_state(xml);
}
void User_state::report_pointer_position(Xml_generator &xml) const
{
xml.attribute("xpos", _pointer_pos.x());
xml.attribute("ypos", _pointer_pos.y());
}
2011-12-22 16:19:25 +01:00
void User_state::report_hovered_view_owner(Xml_generator &xml) const
2011-12-22 16:19:25 +01:00
{
if (_hovered)
_hovered->report(xml);
}
2013-09-07 02:02:26 +02:00
void User_state::report_focused_view_owner(Xml_generator &xml, bool active) const
{
if (_focused) {
_focused->report(xml);
if (active) xml.attribute("active", "yes");
}
}
void User_state::report_last_clicked_view_owner(Xml_generator &xml) const
{
if (_last_clicked)
_last_clicked->report(xml);
}
void User_state::forget(View_owner const &owner)
{
_focus.forget(owner);
if (&owner == _focused) _focused = nullptr;
if (&owner == _next_focused) _next_focused = nullptr;
if (&owner == _last_clicked) _last_clicked = nullptr;
if (_hovered == &owner) {
View_component * const pointed_view = _view_stack.find_view(_pointer_pos);
_hovered = pointed_view ? &pointed_view->owner() : nullptr;
}
if (_input_receiver == &owner)
_input_receiver = nullptr;
2011-12-22 16:19:25 +01:00
}
bool User_state::_focus_change_permitted(View_owner const &caller) const
{
/*
* If no session is focused, we allow any client to assign it. This
* is useful for programs such as an initial login window that
* should receive input events without prior manual selection via
* the mouse.
*
* In principle, a client could steal the focus during time between
* a currently focused session gets closed and before the user
* manually picks a new session. However, in practice, the focus
* policy during application startup and exit is managed by a
* window manager that sits between nitpicker and the application.
*/
if (!_focused)
return true;
/*
* Check if the currently focused session label belongs to a
* session subordinated to the caller, i.e., it originated from
* a child of the caller or from the same process. This is the
* case if the first part of the focused session label is
* identical to the caller's label.
*/
char const * const focused_label = _focused->label().string();
char const * const caller_label = caller.label().string();
return strcmp(focused_label, caller_label, strlen(caller_label)) == 0;
}
void User_state::_focus_view_owner_via_click(View_owner &owner)
{
_next_focused = &owner;
_focused = &owner;
_focus.assign(owner);
if (!_global_key_sequence)
_input_receiver = &owner;
}