nitpicker: Support for session-focus management
This patch introduces a focus-management facility to the nitpicker session interface. As a side effect of this change, we remove the notion of a "focused view". There can only be a "focused session". This makes sense because input is directed to sessions, not views. Issue #1168
This commit is contained in:
parent
d22cddd1e8
commit
24869bd3ff
|
@ -25,26 +25,29 @@ struct Nitpicker::Session_client : public Genode::Rpc_client<Session>
|
||||||
explicit Session_client(Session_capability session)
|
explicit Session_client(Session_capability session)
|
||||||
: Rpc_client<Session>(session) { }
|
: Rpc_client<Session>(session) { }
|
||||||
|
|
||||||
Framebuffer::Session_capability framebuffer_session() {
|
Framebuffer::Session_capability framebuffer_session() override {
|
||||||
return call<Rpc_framebuffer_session>(); }
|
return call<Rpc_framebuffer_session>(); }
|
||||||
|
|
||||||
Input::Session_capability input_session() {
|
Input::Session_capability input_session() override {
|
||||||
return call<Rpc_input_session>(); }
|
return call<Rpc_input_session>(); }
|
||||||
|
|
||||||
View_capability create_view(View_capability parent = View_capability()) {
|
View_capability create_view(View_capability parent = View_capability()) override {
|
||||||
return call<Rpc_create_view>(parent); }
|
return call<Rpc_create_view>(parent); }
|
||||||
|
|
||||||
void destroy_view(View_capability view) {
|
void destroy_view(View_capability view) override {
|
||||||
call<Rpc_destroy_view>(view); }
|
call<Rpc_destroy_view>(view); }
|
||||||
|
|
||||||
int background(View_capability view) {
|
int background(View_capability view) override {
|
||||||
return call<Rpc_background>(view); }
|
return call<Rpc_background>(view); }
|
||||||
|
|
||||||
Framebuffer::Mode mode() {
|
Framebuffer::Mode mode() override {
|
||||||
return call<Rpc_mode>(); }
|
return call<Rpc_mode>(); }
|
||||||
|
|
||||||
void buffer(Framebuffer::Mode mode, bool alpha) {
|
void buffer(Framebuffer::Mode mode, bool alpha) override {
|
||||||
call<Rpc_buffer>(mode, alpha); }
|
call<Rpc_buffer>(mode, alpha); }
|
||||||
|
|
||||||
|
void focus(Nitpicker::Session_capability session) override {
|
||||||
|
call<Rpc_focus>(session); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _INCLUDE__NITPICKER_SESSION__CLIENT_H_ */
|
#endif /* _INCLUDE__NITPICKER_SESSION__CLIENT_H_ */
|
||||||
|
|
|
@ -85,6 +85,20 @@ struct Nitpicker::Session : Genode::Session
|
||||||
*/
|
*/
|
||||||
virtual void buffer(Framebuffer::Mode mode, bool use_alpha) = 0;
|
virtual void buffer(Framebuffer::Mode mode, bool use_alpha) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set focused session
|
||||||
|
*
|
||||||
|
* Normally, the focused session is defined by the user by clicking on a
|
||||||
|
* view. The 'focus' function allows a client to set the focus without user
|
||||||
|
* action. However, the change of the focus is performed only is the
|
||||||
|
* currently focused session belongs to a child or the same process as the
|
||||||
|
* called session. This relationship is checked by comparing the session
|
||||||
|
* labels of the currently focused session and the caller. This way, a
|
||||||
|
* common parent can manage the focus among its child processes. But a
|
||||||
|
* session cannot steal the focus from an unrelated session.
|
||||||
|
*/
|
||||||
|
virtual void focus(Genode::Capability<Session> focused) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return number of bytes needed for virtual framebuffer of specified size
|
* Return number of bytes needed for virtual framebuffer of specified size
|
||||||
*/
|
*/
|
||||||
|
@ -108,12 +122,13 @@ struct Nitpicker::Session : Genode::Session
|
||||||
GENODE_RPC(Rpc_destroy_view, void, destroy_view, View_capability);
|
GENODE_RPC(Rpc_destroy_view, void, destroy_view, View_capability);
|
||||||
GENODE_RPC(Rpc_background, int, background, View_capability);
|
GENODE_RPC(Rpc_background, int, background, View_capability);
|
||||||
GENODE_RPC(Rpc_mode, Framebuffer::Mode, mode);
|
GENODE_RPC(Rpc_mode, Framebuffer::Mode, mode);
|
||||||
|
GENODE_RPC(Rpc_focus, void, focus, Genode::Capability<Session>);
|
||||||
GENODE_RPC_THROW(Rpc_buffer, void, buffer, GENODE_TYPE_LIST(Out_of_metadata),
|
GENODE_RPC_THROW(Rpc_buffer, void, buffer, GENODE_TYPE_LIST(Out_of_metadata),
|
||||||
Framebuffer::Mode, bool);
|
Framebuffer::Mode, bool);
|
||||||
|
|
||||||
GENODE_RPC_INTERFACE(Rpc_framebuffer_session, Rpc_input_session,
|
GENODE_RPC_INTERFACE(Rpc_framebuffer_session, Rpc_input_session,
|
||||||
Rpc_create_view, Rpc_destroy_view, Rpc_background,
|
Rpc_create_view, Rpc_destroy_view, Rpc_background,
|
||||||
Rpc_mode, Rpc_buffer);
|
Rpc_mode, Rpc_buffer, Rpc_focus);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _INCLUDE__NITPICKER_SESSION__NITPICKER_SESSION_H_ */
|
#endif /* _INCLUDE__NITPICKER_SESSION__NITPICKER_SESSION_H_ */
|
||||||
|
|
|
@ -216,33 +216,33 @@ namespace Nitpicker {
|
||||||
** Nitpicker session interface **
|
** Nitpicker session interface **
|
||||||
*********************************/
|
*********************************/
|
||||||
|
|
||||||
Framebuffer::Session_capability framebuffer_session()
|
Framebuffer::Session_capability framebuffer_session() override
|
||||||
{
|
{
|
||||||
return _nitpicker.framebuffer_session();
|
return _nitpicker.framebuffer_session();
|
||||||
}
|
}
|
||||||
|
|
||||||
Input::Session_capability input_session()
|
Input::Session_capability input_session() override
|
||||||
{
|
{
|
||||||
return _proxy_input_cap;
|
return _proxy_input_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
View_capability create_view(View_capability)
|
View_capability create_view(View_capability) override
|
||||||
{
|
{
|
||||||
return _proxy_view_cap;
|
return _proxy_view_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_view(View_capability view)
|
void destroy_view(View_capability view) override
|
||||||
{
|
{
|
||||||
_nitpicker.destroy_view(_nitpicker_view);
|
_nitpicker.destroy_view(_nitpicker_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
int background(View_capability view)
|
int background(View_capability view) override
|
||||||
{
|
{
|
||||||
/* not forwarding to real nitpicker session */
|
/* not forwarding to real nitpicker session */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Framebuffer::Mode mode()
|
Framebuffer::Mode mode() override
|
||||||
{
|
{
|
||||||
int mode_width = (_max_width > -1) ?
|
int mode_width = (_max_width > -1) ?
|
||||||
_max_width :
|
_max_width :
|
||||||
|
@ -256,11 +256,13 @@ namespace Nitpicker {
|
||||||
_nitpicker.mode().format());
|
_nitpicker.mode().format());
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer(Framebuffer::Mode mode, bool use_alpha)
|
void buffer(Framebuffer::Mode mode, bool use_alpha) override
|
||||||
{
|
{
|
||||||
_nitpicker.buffer(mode, use_alpha);
|
_nitpicker.buffer(mode, use_alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void focus(Capability<Session>) override { }
|
||||||
|
|
||||||
|
|
||||||
/**********************************
|
/**********************************
|
||||||
** Input::Transformer interface **
|
** Input::Transformer interface **
|
||||||
|
|
|
@ -100,9 +100,8 @@ class Chunky_menubar : public Texture<PT>,
|
||||||
Color(r / 4, g / 4, b / 4));
|
Color(r / 4, g / 4, b / 4));
|
||||||
|
|
||||||
/* draw label */
|
/* draw label */
|
||||||
draw_label(_canvas, view_rect.center(label_size(session_label.string(),
|
draw_label(_canvas, view_rect.center(label_size(session_label.string(), "")),
|
||||||
view_title.string())), session_label.string(),
|
session_label.string(), WHITE, "", session_color);
|
||||||
WHITE, view_title.string(), session_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using Menubar::state;
|
using Menubar::state;
|
||||||
|
|
|
@ -456,6 +456,8 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
|
|
||||||
View_stack &_view_stack;
|
View_stack &_view_stack;
|
||||||
|
|
||||||
|
Mode &_mode;
|
||||||
|
|
||||||
List<View_component> _view_list;
|
List<View_component> _view_list;
|
||||||
|
|
||||||
/* capabilities for sub sessions */
|
/* capabilities for sub sessions */
|
||||||
|
@ -487,6 +489,38 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
_buffer_size = 0;
|
_buffer_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _focus_change_permitted() const
|
||||||
|
{
|
||||||
|
::Session * const focused_session = _mode.focused_session();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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_session)
|
||||||
|
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_session->label().string();
|
||||||
|
char const * const caller_label = label().string();
|
||||||
|
|
||||||
|
return strcmp(focused_label, caller_label, Genode::strlen(caller_label)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -494,6 +528,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
*/
|
*/
|
||||||
Session_component(Session_label const &label,
|
Session_component(Session_label const &label,
|
||||||
View_stack &view_stack,
|
View_stack &view_stack,
|
||||||
|
Mode &mode,
|
||||||
Rpc_entrypoint &ep,
|
Rpc_entrypoint &ep,
|
||||||
Framebuffer::Session &framebuffer,
|
Framebuffer::Session &framebuffer,
|
||||||
int v_offset,
|
int v_offset,
|
||||||
|
@ -506,7 +541,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
_buffer_alloc(&buffer_alloc, ram_quota),
|
_buffer_alloc(&buffer_alloc, ram_quota),
|
||||||
_framebuffer(framebuffer),
|
_framebuffer(framebuffer),
|
||||||
_framebuffer_session_component(view_stack, *this, framebuffer, *this),
|
_framebuffer_session_component(view_stack, *this, framebuffer, *this),
|
||||||
_ep(ep), _view_stack(view_stack),
|
_ep(ep), _view_stack(view_stack), _mode(mode),
|
||||||
_framebuffer_session_cap(_ep.manage(&_framebuffer_session_component)),
|
_framebuffer_session_cap(_ep.manage(&_framebuffer_session_component)),
|
||||||
_input_session_cap(_ep.manage(&_input_session_component)),
|
_input_session_cap(_ep.manage(&_input_session_component)),
|
||||||
_provides_default_bg(provides_default_bg)
|
_provides_default_bg(provides_default_bg)
|
||||||
|
@ -560,13 +595,13 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
** Nitpicker session interface **
|
** Nitpicker session interface **
|
||||||
*********************************/
|
*********************************/
|
||||||
|
|
||||||
Framebuffer::Session_capability framebuffer_session() {
|
Framebuffer::Session_capability framebuffer_session() override {
|
||||||
return _framebuffer_session_cap; }
|
return _framebuffer_session_cap; }
|
||||||
|
|
||||||
Input::Session_capability input_session() {
|
Input::Session_capability input_session() override {
|
||||||
return _input_session_cap; }
|
return _input_session_cap; }
|
||||||
|
|
||||||
View_capability create_view(View_capability parent_cap)
|
View_capability create_view(View_capability parent_cap) override
|
||||||
{
|
{
|
||||||
/* lookup parent view */
|
/* lookup parent view */
|
||||||
View_component *parent_view =
|
View_component *parent_view =
|
||||||
|
@ -590,7 +625,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
return _ep.manage(view);
|
return _ep.manage(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_view(View_capability view_cap)
|
void destroy_view(View_capability view_cap) override
|
||||||
{
|
{
|
||||||
View_component *vc = dynamic_cast<View_component *>(_ep.lookup_and_lock(view_cap));
|
View_component *vc = dynamic_cast<View_component *>(_ep.lookup_and_lock(view_cap));
|
||||||
if (!vc) return;
|
if (!vc) return;
|
||||||
|
@ -601,7 +636,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
destroy(env()->heap(), vc);
|
destroy(env()->heap(), vc);
|
||||||
}
|
}
|
||||||
|
|
||||||
int background(View_capability view_cap)
|
int background(View_capability view_cap) override
|
||||||
{
|
{
|
||||||
if (_provides_default_bg) {
|
if (_provides_default_bg) {
|
||||||
Object_pool<View_component>::Guard vc(_ep.lookup_and_lock(view_cap));
|
Object_pool<View_component>::Guard vc(_ep.lookup_and_lock(view_cap));
|
||||||
|
@ -623,7 +658,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Framebuffer::Mode mode()
|
Framebuffer::Mode mode() override
|
||||||
{
|
{
|
||||||
unsigned const width = _framebuffer.mode().width();
|
unsigned const width = _framebuffer.mode().width();
|
||||||
unsigned const height = _framebuffer.mode().height()
|
unsigned const height = _framebuffer.mode().height()
|
||||||
|
@ -633,7 +668,7 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
_framebuffer.mode().format());
|
_framebuffer.mode().format());
|
||||||
}
|
}
|
||||||
|
|
||||||
void buffer(Framebuffer::Mode mode, bool use_alpha)
|
void buffer(Framebuffer::Mode mode, bool use_alpha) override
|
||||||
{
|
{
|
||||||
/* check if the session quota suffices for the specified mode */
|
/* check if the session quota suffices for the specified mode */
|
||||||
if (_buffer_alloc.quota() < ram_quota(mode, use_alpha))
|
if (_buffer_alloc.quota() < ram_quota(mode, use_alpha))
|
||||||
|
@ -642,6 +677,28 @@ class Nitpicker::Session_component : public Genode::Rpc_object<Session>,
|
||||||
_framebuffer_session_component.notify_mode_change(mode, use_alpha);
|
_framebuffer_session_component.notify_mode_change(mode, use_alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void focus(Genode::Capability<Nitpicker::Session> session_cap) override
|
||||||
|
{
|
||||||
|
/* check permission by comparing session labels */
|
||||||
|
if (!_focus_change_permitted()) {
|
||||||
|
PWRN("unauthorized focus change requesed by %s", label().string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prevent focus changes during drag operations */
|
||||||
|
if (_mode.drag())
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* lookup targeted session object */
|
||||||
|
Session_component * const session =
|
||||||
|
session_cap.valid() ? dynamic_cast<Session_component *>(_ep.lookup_and_lock(session_cap)) : 0;
|
||||||
|
|
||||||
|
_mode.focused_session(session);
|
||||||
|
|
||||||
|
if (session)
|
||||||
|
session->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************
|
/*******************************
|
||||||
** Buffer_provider interface **
|
** Buffer_provider interface **
|
||||||
|
@ -684,6 +741,7 @@ class Nitpicker::Root : public Genode::Root_component<Session_component>
|
||||||
Global_keys &_global_keys;
|
Global_keys &_global_keys;
|
||||||
Framebuffer::Mode _scr_mode;
|
Framebuffer::Mode _scr_mode;
|
||||||
View_stack &_view_stack;
|
View_stack &_view_stack;
|
||||||
|
Mode &_mode;
|
||||||
Framebuffer::Session &_framebuffer;
|
Framebuffer::Session &_framebuffer;
|
||||||
int _default_v_offset;
|
int _default_v_offset;
|
||||||
|
|
||||||
|
@ -712,7 +770,7 @@ class Nitpicker::Root : public Genode::Root_component<Session_component>
|
||||||
bool const provides_default_bg = (strcmp(label.string(), "backdrop") == 0);
|
bool const provides_default_bg = (strcmp(label.string(), "backdrop") == 0);
|
||||||
|
|
||||||
Session_component *session = new (md_alloc())
|
Session_component *session = new (md_alloc())
|
||||||
Session_component(Session_label(args), _view_stack, *ep(),
|
Session_component(Session_label(args), _view_stack, _mode, *ep(),
|
||||||
_framebuffer, v_offset, provides_default_bg,
|
_framebuffer, v_offset, provides_default_bg,
|
||||||
stay_top, *md_alloc(), unused_quota);
|
stay_top, *md_alloc(), unused_quota);
|
||||||
|
|
||||||
|
@ -733,6 +791,7 @@ class Nitpicker::Root : public Genode::Root_component<Session_component>
|
||||||
{
|
{
|
||||||
_session_list.remove(session);
|
_session_list.remove(session);
|
||||||
_global_keys.apply_config(_session_list);
|
_global_keys.apply_config(_session_list);
|
||||||
|
_mode.forget(*session);
|
||||||
|
|
||||||
destroy(md_alloc(), session);
|
destroy(md_alloc(), session);
|
||||||
}
|
}
|
||||||
|
@ -744,13 +803,13 @@ class Nitpicker::Root : public Genode::Root_component<Session_component>
|
||||||
*/
|
*/
|
||||||
Root(Session_list &session_list, Global_keys &global_keys,
|
Root(Session_list &session_list, Global_keys &global_keys,
|
||||||
Rpc_entrypoint &session_ep, View_stack &view_stack,
|
Rpc_entrypoint &session_ep, View_stack &view_stack,
|
||||||
Allocator &md_alloc,
|
Mode &mode, Allocator &md_alloc,
|
||||||
Framebuffer::Session &framebuffer,
|
Framebuffer::Session &framebuffer,
|
||||||
int default_v_offset)
|
int default_v_offset)
|
||||||
:
|
:
|
||||||
Root_component<Session_component>(&session_ep, &md_alloc),
|
Root_component<Session_component>(&session_ep, &md_alloc),
|
||||||
_session_list(session_list), _global_keys(global_keys),
|
_session_list(session_list), _global_keys(global_keys),
|
||||||
_view_stack(view_stack),
|
_view_stack(view_stack), _mode(mode),
|
||||||
_framebuffer(framebuffer),
|
_framebuffer(framebuffer),
|
||||||
_default_v_offset(default_v_offset)
|
_default_v_offset(default_v_offset)
|
||||||
{ }
|
{ }
|
||||||
|
@ -843,7 +902,8 @@ struct Nitpicker::Main
|
||||||
Genode::Sliced_heap sliced_heap = { env()->ram_session(), env()->rm_session() };
|
Genode::Sliced_heap sliced_heap = { env()->ram_session(), env()->rm_session() };
|
||||||
|
|
||||||
Root<PT> np_root = { session_list, global_keys, ep.rpc_ep(), user_state,
|
Root<PT> np_root = { session_list, global_keys, ep.rpc_ep(), user_state,
|
||||||
sliced_heap, framebuffer, Framebuffer_screen::MENUBAR_HEIGHT };
|
user_state, sliced_heap, framebuffer,
|
||||||
|
Framebuffer_screen::MENUBAR_HEIGHT };
|
||||||
|
|
||||||
Genode::Reporter pointer_reporter = { "pointer" };
|
Genode::Reporter pointer_reporter = { "pointer" };
|
||||||
|
|
||||||
|
@ -874,7 +934,7 @@ struct Nitpicker::Main
|
||||||
{
|
{
|
||||||
// tmp_fb = &framebuffer;
|
// tmp_fb = &framebuffer;
|
||||||
|
|
||||||
fb_screen->menubar.state(Menubar_state(user_state, "", "", BLACK));
|
fb_screen->menubar.state(Menubar_state(user_state, "", BLACK));
|
||||||
|
|
||||||
user_state.default_background(background);
|
user_state.default_background(background);
|
||||||
user_state.stack(mouse_cursor);
|
user_state.stack(mouse_cursor);
|
||||||
|
|
|
@ -21,15 +21,12 @@
|
||||||
struct Menubar_state
|
struct Menubar_state
|
||||||
{
|
{
|
||||||
Genode::String<128> session_label;
|
Genode::String<128> session_label;
|
||||||
Genode::String<128> view_title;
|
|
||||||
Mode mode;
|
Mode mode;
|
||||||
Color session_color;
|
Color session_color;
|
||||||
|
|
||||||
Menubar_state(Mode mode, char const *session_label,
|
Menubar_state(Mode mode, char const *session_label, Color session_color)
|
||||||
char const *view_title, Color session_color)
|
|
||||||
:
|
:
|
||||||
session_label(session_label), view_title(view_title),
|
session_label(session_label), mode(mode), session_color(session_color)
|
||||||
mode(mode), session_color(session_color)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Menubar_state() : session_color(BLACK) { }
|
Menubar_state() : session_color(BLACK) { }
|
||||||
|
|
|
@ -14,26 +14,26 @@
|
||||||
#ifndef _MODE_H_
|
#ifndef _MODE_H_
|
||||||
#define _MODE_H_
|
#define _MODE_H_
|
||||||
|
|
||||||
class View;
|
class Session;
|
||||||
class Canvas_base;
|
|
||||||
|
|
||||||
class Mode
|
class Mode
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool _xray;
|
bool _xray = false;
|
||||||
bool _kill;
|
bool _kill = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Last clicked view. This view is receiving keyboard input, except
|
* Number of currently pressed keys.
|
||||||
* for global keys.
|
* This counter is used to determine if the user
|
||||||
|
* is dragging an item.
|
||||||
*/
|
*/
|
||||||
View const *_focused_view;
|
unsigned _key_cnt = 0;
|
||||||
|
|
||||||
|
Session *_focused_session = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Mode(): _xray(false), _kill(false), _focused_view(0) { }
|
|
||||||
|
|
||||||
virtual ~Mode() { }
|
virtual ~Mode() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,20 +42,30 @@ class Mode
|
||||||
bool xray() const { return _xray; }
|
bool xray() const { return _xray; }
|
||||||
bool kill() const { return _kill; }
|
bool kill() const { return _kill; }
|
||||||
bool flat() const { return !_xray && !_kill; }
|
bool flat() const { return !_xray && !_kill; }
|
||||||
|
bool drag() const { return _key_cnt > 0; }
|
||||||
|
|
||||||
void leave_kill() { _kill = false; }
|
void leave_kill() { _kill = false; }
|
||||||
void toggle_kill() { _kill = !_kill; }
|
void toggle_kill() { _kill = !_kill; }
|
||||||
void toggle_xray() { _xray = !_xray; }
|
void toggle_xray() { _xray = !_xray; }
|
||||||
|
|
||||||
View const *focused_view() const { return _focused_view; }
|
void inc_key_cnt() { _key_cnt++; }
|
||||||
|
void dec_key_cnt() { _key_cnt--; }
|
||||||
|
|
||||||
void focused_view(View const *view) { _focused_view = view; }
|
bool has_key_cnt(unsigned cnt) const { return cnt == _key_cnt; }
|
||||||
|
|
||||||
|
Session *focused_session() { return _focused_session; }
|
||||||
|
|
||||||
|
virtual void focused_session(Session *session) { _focused_session = session; }
|
||||||
|
|
||||||
|
bool is_focused(Session const &session) const { return &session == _focused_session; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discard all references to specified view
|
* Discard all references to specified view
|
||||||
*/
|
*/
|
||||||
virtual void forget(View const &v) {
|
virtual void forget(Session const &session)
|
||||||
if (&v == _focused_view) _focused_view = 0; }
|
{
|
||||||
|
if (is_focused(session)) _focused_session = nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,12 +37,24 @@ static inline bool _mouse_button(Keycode keycode) {
|
||||||
|
|
||||||
User_state::User_state(Global_keys &global_keys, Area view_stack_size, Menubar &menubar)
|
User_state::User_state(Global_keys &global_keys, Area view_stack_size, Menubar &menubar)
|
||||||
:
|
:
|
||||||
View_stack(view_stack_size, *this), _global_keys(global_keys), _key_cnt(0),
|
View_stack(view_stack_size, *this), _global_keys(global_keys), _menubar(menubar)
|
||||||
_menubar(menubar), _pointed_view(0), _input_receiver(0),
|
|
||||||
_global_key_sequence(false)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
void User_state::_update_all()
|
||||||
|
{
|
||||||
|
Menubar_state state(*this, "", BLACK);
|
||||||
|
|
||||||
|
if (_input_receiver)
|
||||||
|
state = Menubar_state(*this,
|
||||||
|
_input_receiver->label().string(),
|
||||||
|
_input_receiver->color());
|
||||||
|
|
||||||
|
_menubar.state(state);
|
||||||
|
update_all_views();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void User_state::handle_event(Input::Event ev)
|
void User_state::handle_event(Input::Event ev)
|
||||||
{
|
{
|
||||||
Input::Keycode const keycode = ev.keycode();
|
Input::Keycode const keycode = ev.keycode();
|
||||||
|
@ -77,22 +89,22 @@ void User_state::handle_event(Input::Event ev)
|
||||||
_mouse_pos = Point(ax, ay);
|
_mouse_pos = Point(ax, ay);
|
||||||
|
|
||||||
/* count keys */
|
/* count keys */
|
||||||
if (type == Event::PRESS) _key_cnt++;
|
if (type == Event::PRESS) Mode::inc_key_cnt();
|
||||||
if (type == Event::RELEASE && _key_cnt > 0) _key_cnt--;
|
if (type == Event::RELEASE && Mode::drag()) Mode::dec_key_cnt();
|
||||||
|
|
||||||
View const * const pointed_view = find_view(_mouse_pos);
|
View const * const pointed_view = find_view(_mouse_pos);
|
||||||
|
Session * const pointed_session = pointed_view ? &pointed_view->session() : 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deliver a leave event if pointed-to session changed
|
* Deliver a leave event if pointed-to session changed
|
||||||
*/
|
*/
|
||||||
if (pointed_view && _pointed_view &&
|
if (_pointed_session && (pointed_session != _pointed_session)) {
|
||||||
!pointed_view->same_session_as(*_pointed_view)) {
|
|
||||||
|
|
||||||
Input::Event leave_ev(Input::Event::LEAVE, 0, ax, ay, 0, 0);
|
Input::Event leave_ev(Input::Event::LEAVE, 0, ax, ay, 0, 0);
|
||||||
_pointed_view->session().submit_input_event(leave_ev);
|
_pointed_session->submit_input_event(leave_ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pointed_view = pointed_view;
|
_pointed_session = pointed_session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guard that, when 'enabled' is set to true, performs a whole-screen
|
* Guard that, when 'enabled' is set to true, performs a whole-screen
|
||||||
|
@ -101,96 +113,63 @@ void User_state::handle_event(Input::Event ev)
|
||||||
struct Update_all_guard
|
struct Update_all_guard
|
||||||
{
|
{
|
||||||
User_state &user_state;
|
User_state &user_state;
|
||||||
bool update_menubar = false;
|
bool update = false;
|
||||||
bool update_views = false;
|
|
||||||
char const *menu_title = "";
|
|
||||||
|
|
||||||
Update_all_guard(User_state &user_state)
|
Update_all_guard(User_state &user_state)
|
||||||
: user_state(user_state) { }
|
: user_state(user_state) { }
|
||||||
|
|
||||||
~Update_all_guard()
|
~Update_all_guard()
|
||||||
{
|
{
|
||||||
Menubar_state state(user_state, "", "", BLACK);
|
if (update)
|
||||||
|
user_state._update_all();
|
||||||
if (user_state._input_receiver)
|
|
||||||
state = Menubar_state(user_state,
|
|
||||||
user_state._input_receiver->label().string(),
|
|
||||||
menu_title,
|
|
||||||
user_state._input_receiver->color());
|
|
||||||
|
|
||||||
if (update_menubar)
|
|
||||||
user_state._menubar.state(state);
|
|
||||||
|
|
||||||
if (update_menubar || update_views)
|
|
||||||
user_state.update_all_views();
|
|
||||||
}
|
}
|
||||||
} update_all_guard(*this);
|
} update_all_guard(*this);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle start of a key sequence
|
* Handle start of a key sequence
|
||||||
*/
|
*/
|
||||||
if (type == Event::PRESS && _key_cnt == 1) {
|
if (type == Event::PRESS && Mode::has_key_cnt(1)) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Detect mouse press event in kill mode, used to select the session
|
* Detect mouse press event in kill mode, used to select the session
|
||||||
* to lock out.
|
* to lock out.
|
||||||
*/
|
*/
|
||||||
if (kill() && keycode == Input::BTN_LEFT) {
|
if (kill() && keycode == Input::BTN_LEFT) {
|
||||||
if (pointed_view)
|
if (_pointed_session)
|
||||||
lock_out_session(pointed_view->session());
|
lock_out_session(*_pointed_session);
|
||||||
|
|
||||||
/* leave kill mode */
|
/* leave kill mode */
|
||||||
update_all_guard.update_menubar = true;
|
update_all_guard.update = true;
|
||||||
update_all_guard.update_views = true;
|
|
||||||
Mode::leave_kill();
|
Mode::leave_kill();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* update focused view */
|
/* update focused session */
|
||||||
if (pointed_view != focused_view() && _mouse_button(keycode)) {
|
if (pointed_session != Mode::focused_session() && _mouse_button(keycode)) {
|
||||||
|
|
||||||
bool const focus_stays_in_session =
|
update_all_guard.update = true;
|
||||||
focused_view() && pointed_view &&
|
|
||||||
focused_view()->belongs_to(pointed_view->session());
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update the whole screen when the focus change results in
|
* Notify both the old focused session and the new one.
|
||||||
* changing the focus to another session.
|
|
||||||
*/
|
*/
|
||||||
if (flat() && !focus_stays_in_session) {
|
if (Mode::focused_session()) {
|
||||||
update_all_guard.update_menubar = true;
|
Input::Event unfocus_ev(Input::Event::FOCUS, 0, ax, ay, 0, 0);
|
||||||
update_all_guard.update_views = true;
|
Mode::focused_session()->submit_input_event(unfocus_ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (_pointed_session) {
|
||||||
* Notify both the old focussed session and the new one.
|
Input::Event focus_ev(Input::Event::FOCUS, 1, ax, ay, 0, 0);
|
||||||
*/
|
pointed_session->submit_input_event(focus_ev);
|
||||||
if (!focus_stays_in_session) {
|
|
||||||
|
|
||||||
if (focused_view()) {
|
|
||||||
Input::Event unfocus_ev(Input::Event::FOCUS, 0, ax, ay, 0, 0);
|
|
||||||
focused_view()->session().submit_input_event(unfocus_ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pointed_view) {
|
|
||||||
Input::Event focus_ev(Input::Event::FOCUS, 1, ax, ay, 0, 0);
|
|
||||||
pointed_view->session().submit_input_event(focus_ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update_all_guard.update_menubar = true;
|
focused_session(_pointed_session);
|
||||||
|
|
||||||
if (!flat() || !focused_view() || !pointed_view)
|
|
||||||
update_all_guard.update_views = true;
|
|
||||||
|
|
||||||
focused_view(pointed_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there exists a global rule for the pressed key, set the
|
* If there exists a global rule for the pressed key, set the
|
||||||
* corresponding session as receiver of the input stream until the key
|
* corresponding session as receiver of the input stream until the key
|
||||||
* count reaches zero. Otherwise, the input stream is directed to the
|
* count reaches zero. Otherwise, the input stream is directed to the
|
||||||
* pointed-at view.
|
* pointed-at session.
|
||||||
*
|
*
|
||||||
* If we deliver a global key sequence, we temporarily change the focus
|
* If we deliver a global key sequence, we temporarily change the focus
|
||||||
* to the global receiver. To reflect that change, we need to update
|
* to the global receiver. To reflect that change, we need to update
|
||||||
|
@ -198,20 +177,17 @@ void User_state::handle_event(Input::Event ev)
|
||||||
*/
|
*/
|
||||||
Session * const global_receiver = _global_keys.global_receiver(keycode);
|
Session * const global_receiver = _global_keys.global_receiver(keycode);
|
||||||
if (global_receiver) {
|
if (global_receiver) {
|
||||||
_global_key_sequence = true;
|
_global_key_sequence = true;
|
||||||
_input_receiver = global_receiver;
|
_input_receiver = global_receiver;
|
||||||
update_all_guard.menu_title = "";
|
update_all_guard.update = true;
|
||||||
update_all_guard.update_menubar = true;
|
|
||||||
update_all_guard.update_views = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* No global rule matched, so the input stream gets directed to the
|
* No global rule matched, so the input stream gets directed to the
|
||||||
* focused view or refers to a built-in operation.
|
* focused session or refers to a built-in operation.
|
||||||
*/
|
*/
|
||||||
if (!global_receiver && focused_view()) {
|
if (!global_receiver) {
|
||||||
_input_receiver = &focused_view()->session();
|
_input_receiver = Mode::focused_session();
|
||||||
update_all_guard.menu_title = focused_view()->title();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -220,13 +196,14 @@ void User_state::handle_event(Input::Event ev)
|
||||||
*/
|
*/
|
||||||
if (_global_keys.is_operation_key(keycode)) {
|
if (_global_keys.is_operation_key(keycode)) {
|
||||||
|
|
||||||
if (_global_keys.is_kill_key(keycode)) Mode::toggle_kill();
|
if (_global_keys.is_kill_key(keycode)) {
|
||||||
|
Mode::toggle_kill();
|
||||||
|
_input_receiver = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (_global_keys.is_xray_key(keycode)) Mode::toggle_xray();
|
if (_global_keys.is_xray_key(keycode)) Mode::toggle_xray();
|
||||||
|
|
||||||
update_all_guard.update_menubar = true;
|
update_all_guard.update = true;
|
||||||
update_all_guard.update_views = true;
|
|
||||||
|
|
||||||
_input_receiver = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,22 +214,22 @@ void User_state::handle_event(Input::Event ev)
|
||||||
|
|
||||||
if (type == Event::MOTION || type == Event::WHEEL) {
|
if (type == Event::MOTION || type == Event::WHEEL) {
|
||||||
|
|
||||||
if (_key_cnt == 0) {
|
if (Mode::has_key_cnt(0)) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In flat mode, we deliver motion events to the session of
|
* In flat mode, we deliver motion events to the pointed-at
|
||||||
* the pointed view. In xray mode, we deliver motion
|
* session. In xray mode, we deliver motion events only to the
|
||||||
* events only to the session with the focused view.
|
* focused session.
|
||||||
*/
|
*/
|
||||||
if (flat() || (xray() && focused_view() == pointed_view))
|
if (flat() || (xray() && Mode::focused_session() == pointed_session))
|
||||||
if (pointed_view)
|
if (pointed_session)
|
||||||
pointed_view->session().submit_input_event(ev);
|
pointed_session->submit_input_event(ev);
|
||||||
|
|
||||||
} else if (_input_receiver)
|
} else if (_input_receiver)
|
||||||
_input_receiver->submit_input_event(ev);
|
_input_receiver->submit_input_event(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* deliver press/release event to session with focused view */
|
/* deliver press/release event to focused session */
|
||||||
if (type == Event::PRESS || type == Event::RELEASE)
|
if (type == Event::PRESS || type == Event::RELEASE)
|
||||||
if (_input_receiver)
|
if (_input_receiver)
|
||||||
_input_receiver->submit_input_event(ev);
|
_input_receiver->submit_input_event(ev);
|
||||||
|
@ -260,13 +237,11 @@ void User_state::handle_event(Input::Event ev)
|
||||||
/*
|
/*
|
||||||
* Detect end of global key sequence
|
* Detect end of global key sequence
|
||||||
*/
|
*/
|
||||||
if (ev.type() == Event::RELEASE && _key_cnt == 0 && _global_key_sequence) {
|
if (ev.type() == Event::RELEASE && Mode::has_key_cnt(0) && _global_key_sequence) {
|
||||||
|
|
||||||
_input_receiver = focused_view() ? &focused_view()->session() : 0;
|
_input_receiver = Mode::focused_session();
|
||||||
|
|
||||||
update_all_guard.menu_title = focused_view() ? focused_view()->title() : "";
|
update_all_guard.update = true;
|
||||||
update_all_guard.update_menubar = true;
|
|
||||||
update_all_guard.update_views = true;
|
|
||||||
|
|
||||||
_global_key_sequence = false;
|
_global_key_sequence = false;
|
||||||
}
|
}
|
||||||
|
@ -277,17 +252,23 @@ void User_state::handle_event(Input::Event ev)
|
||||||
** Mode interface **
|
** Mode interface **
|
||||||
********************/
|
********************/
|
||||||
|
|
||||||
void User_state::forget(View const &view)
|
void User_state::forget(Session const &session)
|
||||||
{
|
{
|
||||||
if (focused_view() == &view) {
|
Mode::forget(session);
|
||||||
Mode::forget(view);
|
|
||||||
_menubar.state(Menubar_state(*this, "", "", BLACK));
|
|
||||||
update_all_views();
|
|
||||||
}
|
|
||||||
if (_input_receiver && view.belongs_to(*_input_receiver))
|
|
||||||
_input_receiver = 0;
|
|
||||||
|
|
||||||
if (_pointed_view == &view)
|
if (_pointed_session == &session) {
|
||||||
_pointed_view = find_view(_mouse_pos);
|
View * const pointed_view = find_view(_mouse_pos);
|
||||||
|
_pointed_session = pointed_view ? &pointed_view->session() : nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void User_state::focused_session(Session *session)
|
||||||
|
{
|
||||||
|
Mode::focused_session(session);
|
||||||
|
|
||||||
|
if (!_global_key_sequence)
|
||||||
|
_input_receiver = session;
|
||||||
|
|
||||||
|
_update_all();
|
||||||
|
}
|
||||||
|
|
|
@ -32,17 +32,10 @@ class User_state : public Mode, public View_stack
|
||||||
*/
|
*/
|
||||||
Global_keys &_global_keys;
|
Global_keys &_global_keys;
|
||||||
|
|
||||||
/*
|
|
||||||
* Number of currently pressed keys.
|
|
||||||
* This counter is used to determine if the user
|
|
||||||
* is dragging an item.
|
|
||||||
*/
|
|
||||||
unsigned _key_cnt;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Menubar to display trusted labeling information
|
* Menubar to display trusted labeling information
|
||||||
* according to the current Mitpicker mode and the
|
* according to the current Mitpicker mode and the
|
||||||
* focused view.
|
* focused session.
|
||||||
*/
|
*/
|
||||||
Menubar &_menubar;
|
Menubar &_menubar;
|
||||||
|
|
||||||
|
@ -52,19 +45,21 @@ class User_state : public Mode, public View_stack
|
||||||
Point _mouse_pos;
|
Point _mouse_pos;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Currently pointed-at view
|
* Currently pointed-at session
|
||||||
*/
|
*/
|
||||||
View const *_pointed_view;
|
Session *_pointed_session = nullptr;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Session that receives the current stream of input events
|
* Session that receives the current stream of input events
|
||||||
*/
|
*/
|
||||||
Session *_input_receiver;
|
Session *_input_receiver = nullptr;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* True while a global key sequence is processed
|
* True while a global key sequence is processed
|
||||||
*/
|
*/
|
||||||
bool _global_key_sequence;
|
bool _global_key_sequence = false;
|
||||||
|
|
||||||
|
void _update_all();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -89,7 +84,8 @@ class User_state : public Mode, public View_stack
|
||||||
/**
|
/**
|
||||||
* Mode interface
|
* Mode interface
|
||||||
*/
|
*/
|
||||||
void forget(View const &) override;
|
void forget(Session const &) override;
|
||||||
|
void focused_session(Session *) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -80,8 +80,7 @@ void View::frame(Canvas_base &canvas, Mode const &mode) const
|
||||||
void View::draw(Canvas_base &canvas, Mode const &mode) const
|
void View::draw(Canvas_base &canvas, Mode const &mode) const
|
||||||
{
|
{
|
||||||
/* is this the currently focused view? */
|
/* is this the currently focused view? */
|
||||||
bool const view_is_focused = mode.focused_view()
|
bool const session_is_focused = mode.is_focused(_session);
|
||||||
&& mode.focused_view()->belongs_to(_session);
|
|
||||||
|
|
||||||
Color const frame_color = _session.color();
|
Color const frame_color = _session.color();
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ void View::draw(Canvas_base &canvas, Mode const &mode) const
|
||||||
* Use dimming in x-ray and kill mode, but do not dim the focused view in
|
* Use dimming in x-ray and kill mode, but do not dim the focused view in
|
||||||
* x-ray mode.
|
* x-ray mode.
|
||||||
*/
|
*/
|
||||||
Texture_painter::Mode const op = mode.flat() || (mode.xray() && view_is_focused)
|
Texture_painter::Mode const op = mode.flat() || (mode.xray() && session_is_focused)
|
||||||
? Texture_painter::SOLID : Texture_painter::MIXED;
|
? Texture_painter::SOLID : Texture_painter::MIXED;
|
||||||
|
|
||||||
Rect const view_rect = abs_geometry();
|
Rect const view_rect = abs_geometry();
|
||||||
|
|
|
@ -140,11 +140,7 @@ class View : public Same_buffer_list_elem,
|
||||||
*/
|
*/
|
||||||
virtual int frame_size(Mode const &mode) const
|
virtual int frame_size(Mode const &mode) const
|
||||||
{
|
{
|
||||||
if (mode.focused_view()
|
return mode.is_focused(_session) ? 5 : 3;
|
||||||
&& mode.focused_view()->belongs_to(_session))
|
|
||||||
return 5;
|
|
||||||
|
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,11 +40,10 @@ static View const *last_stay_top_view(View const *view)
|
||||||
template <typename VIEW>
|
template <typename VIEW>
|
||||||
VIEW *View_stack::_next_view(VIEW &view) const
|
VIEW *View_stack::_next_view(VIEW &view) const
|
||||||
{
|
{
|
||||||
Session * const active_session = _mode.focused_view() ?
|
Session * const focused_session = _mode.focused_session();
|
||||||
&_mode.focused_view()->session() : 0;
|
|
||||||
|
|
||||||
View * const active_background = active_session ?
|
View * const active_background = focused_session ?
|
||||||
active_session->background() : 0;
|
focused_session->background() : 0;
|
||||||
|
|
||||||
for (VIEW *next_view = &view; ;) {
|
for (VIEW *next_view = &view; ;) {
|
||||||
|
|
||||||
|
@ -298,16 +297,5 @@ void View_stack::remove_view(View const &view, bool redraw)
|
||||||
/* exclude view from view stack */
|
/* exclude view from view stack */
|
||||||
_views.remove(&view);
|
_views.remove(&view);
|
||||||
|
|
||||||
/*
|
|
||||||
* Reset focused and pointed-at view if necessary
|
|
||||||
*
|
|
||||||
* Thus must be done after calling '_views.remove' because the new focused
|
|
||||||
* pointer is determined by traversing the view stack. If the to-be-removed
|
|
||||||
* view would still be there, we would re-assign the old pointed-to view as
|
|
||||||
* the current one, resulting in a dangling pointer right after the view
|
|
||||||
* gets destructed by the caller of 'removed_view'.
|
|
||||||
*/
|
|
||||||
_mode.forget(view);
|
|
||||||
|
|
||||||
_dirty_rect.mark_as_dirty(rect);
|
_dirty_rect.mark_as_dirty(rect);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue