genode/repos/gems/src/app/floating_window_layouter/main.cc

584 lines
14 KiB
C++

/*
* \brief Floating window layouter
* \author Norman Feske
* \date 2013-02-14
*/
/*
* Copyright (C) 2013-2014 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.
*/
/* Genode includes */
#include <base/printf.h>
#include <base/signal.h>
#include <os/attached_rom_dataspace.h>
#include <os/reporter.h>
#include <os/session_policy.h>
#include <nitpicker_session/connection.h>
#include <input_session/client.h>
#include <input/event.h>
#include <input/keycodes.h>
#include <rom_session/connection.h>
#include <decorator/xml_utils.h>
/* local includes */
#include "window.h"
#include "user_state.h"
#include "operations.h"
namespace Floating_window_layouter {
struct Main;
static Xml_node xml_lookup_window_by_id(Xml_node node, Window_id const id)
{
char const *tag = "window";
char const *id_attr = "id";
for (node = node.sub_node(tag); ; node = node.next(tag))
if (attribute(node, id_attr, 0UL) == id.value)
return node;
throw Xml_node::Nonexistent_sub_node();
}
/**
* Return true if compound XML node contains a sub node with ID
*/
static bool xml_contains_window_node_with_id(Xml_node node,
Window_id const id)
{
try { xml_lookup_window_by_id(node, id.value); return true; }
catch (Xml_node::Nonexistent_sub_node) { return false; }
}
}
struct Floating_window_layouter::Main : Operations
{
Signal_receiver &sig_rec;
List<Window> windows;
Window *lookup_window_by_id(Window_id const id)
{
for (Window *w = windows.first(); w; w = w->next())
if (w->has_id(id))
return w;
return nullptr;
}
Window const *lookup_window_by_id(Window_id const id) const
{
for (Window const *w = windows.first(); w; w = w->next())
if (w->has_id(id))
return w;
return nullptr;
}
/**
* Apply functor to each window
*
* The functor is called with 'Window const &' as argument.
*/
template <typename FUNC>
void for_each_window(FUNC const &fn) const
{
for (Window const *w = windows.first(); w; w = w->next())
fn(*w);
}
User_state _user_state { *this };
/**************************
** Operations interface **
**************************/
void close(Window_id id) override
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->close();
generate_resize_request_model();
generate_focus_model();
}
void to_front(Window_id id) override
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
if (window != windows.first()) {
windows.remove(window);
windows.insert(window);
window->topped();
generate_window_layout_model();
}
}
void focus(Window_id id) override
{
generate_focus_model();
}
void toggle_fullscreen(Window_id id) override
{
/* make sure that the specified window is the front-most one */
to_front(id);
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->is_maximized(!window->is_maximized());
generate_resize_request_model();
}
void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
{
to_front(id);
Window *window = lookup_window_by_id(id);
if (!window)
return;
/*
* The drag operation may result in a change of the window geometry.
* We detect such a change be comparing the original geometry with
* the geometry with the drag operation applied.
*/
Point const last_pos = window->position();
Area const last_requested_size = window->requested_size();
window->drag(element, clicked, curr);
if (last_pos != window->position())
generate_window_layout_model();
if (last_requested_size != window->requested_size())
generate_resize_request_model();
}
void finalize_drag(Window_id id, Window::Element, Point clicked, Point final)
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->finalize_drag_operation();
/*
* Update window layout because highlighting may have changed after the
* drag operation. E.g., if the window has not kept up with the
* dragging of a resize handle, the resize handle is no longer hovered.
*/
generate_window_layout_model();
}
/**
* Install handler for responding to window-list changes
*/
void handle_window_list_update(unsigned);
Signal_dispatcher<Main> window_list_dispatcher = {
sig_rec, *this, &Main::handle_window_list_update };
Attached_rom_dataspace window_list { "window_list" };
/**
* Install handler for responding to focus requests
*/
void handle_focus_request_update(unsigned);
void _apply_focus_request();
int handled_focus_request_id = 0;
Signal_dispatcher<Main> focus_request_dispatcher = {
sig_rec, *this, &Main::handle_focus_request_update };
Attached_rom_dataspace focus_request { "focus_request" };
/**
* Install handler for responding to hover changes
*/
void handle_hover_update(unsigned);
Signal_dispatcher<Main> hover_dispatcher = {
sig_rec, *this, &Main::handle_hover_update };
Attached_rom_dataspace hover { "hover" };
/**
* Install handler for responding to user input
*/
void handle_input(unsigned)
{
while (input.is_pending())
_user_state.handle_input(input_ds.local_addr<Input::Event>(),
input.flush());
}
Signal_dispatcher<Main> input_dispatcher = {
sig_rec, *this, &Main::handle_input };
Nitpicker::Connection nitpicker;
Rect maximized_window_geometry;
void handle_mode_change(unsigned)
{
/* determine maximized window geometry */
Framebuffer::Mode const mode = nitpicker.mode();
/*
* XXX obtain decorator constraints dynamically
*/
enum { PAD_LEFT = 4, PAD_RIGHT = 4, PAD_TOP = 20, PAD_BOTTOM = 4 };
maximized_window_geometry = Rect(Point(PAD_LEFT, PAD_TOP),
Area(mode.width() - PAD_LEFT - PAD_RIGHT,
mode.height() - PAD_TOP - PAD_BOTTOM));
}
Signal_dispatcher<Main> mode_change_dispatcher = {
sig_rec, *this, &Main::handle_mode_change };
Input::Session_client input { nitpicker.input_session() };
Attached_dataspace input_ds { input.dataspace() };
Reporter window_layout_reporter = { "window_layout" };
Reporter resize_request_reporter = { "resize_request" };
Reporter focus_reporter = { "focus" };
bool focused_window_is_maximized() const
{
Window const *w = lookup_window_by_id(_user_state.focused_window_id());
return w && w->is_maximized();
}
void import_window_list(Xml_node);
void generate_window_layout_model();
void generate_resize_request_model();
void generate_focus_model();
/**
* Constructor
*/
Main(Signal_receiver &sig_rec) : sig_rec(sig_rec)
{
nitpicker.mode_sigh(mode_change_dispatcher);
handle_mode_change(0);
window_list.sigh(window_list_dispatcher);
focus_request.sigh(focus_request_dispatcher);
hover.sigh(hover_dispatcher);
input.sigh(input_dispatcher);
window_layout_reporter.enabled(true);
resize_request_reporter.enabled(true);
focus_reporter.enabled(true);
}
};
void Floating_window_layouter::Main::import_window_list(Xml_node window_list_xml)
{
char const *tag = "window";
/*
* Remove windows from layout that are no longer in the window list
*/
for (Window *win = windows.first(), *next = 0; win; win = next) {
next = win->next();
if (!xml_contains_window_node_with_id(window_list_xml, win->id())) {
windows.remove(win);
destroy(env()->heap(), win);
}
}
/*
* Update window attributes, add new windows to the layout
*/
try {
for (Xml_node node = window_list_xml.sub_node(tag); ; node = node.next(tag)) {
unsigned long id = 0;
node.attribute("id").value(&id);
Window *win = lookup_window_by_id(id);
if (!win) {
win = new (env()->heap()) Window(id, maximized_window_geometry);
windows.insert(win);
Point initial_position(150*id % 800, 30 + (100*id % 500));
Window::Label const label = string_attribute(node, "label", Window::Label(""));
win->label(label);
/*
* Evaluate policy configuration for the window label
*/
try {
Session_policy const policy(label);
if (policy.has_attribute("xpos") && policy.has_attribute("ypos"))
initial_position = point_attribute(node);
win->is_maximized(policy.attribute_value("maximized", false));
} catch (Genode::Session_policy::No_policy_defined) { }
win->position(initial_position);
}
win->size(area_attribute(node));
win->title(string_attribute(node, "title", Window::Title("")));
win->has_alpha(node.has_attribute("has_alpha")
&& node.attribute("has_alpha").has_value("yes"));
win->is_hidden(node.has_attribute("hidden")
&& node.attribute("hidden").has_value("yes"));
win->is_resizeable(node.has_attribute("resizeable")
&& node.attribute("resizeable").has_value("yes"));
}
} catch (...) { }
}
void Floating_window_layouter::Main::generate_window_layout_model()
{
Reporter::Xml_generator xml(window_layout_reporter, [&] ()
{
for (Window *w = windows.first(); w; w = w->next()) {
bool const is_hovered = w->has_id(_user_state.hover_state().window_id);
bool const is_focused = w->has_id(_user_state.focused_window_id());
Window::Element const highlight =
is_hovered ? _user_state.hover_state().element : Window::Element::UNDEFINED;
w->serialize(xml, is_focused, highlight);
}
});
}
void Floating_window_layouter::Main::generate_resize_request_model()
{
Reporter::Xml_generator xml(resize_request_reporter, [&] ()
{
for_each_window([&] (Window const &window) {
Area const requested_size = window.requested_size();
if (requested_size != window.size()) {
xml.node("window", [&] () {
xml.attribute("id", window.id().value);
xml.attribute("width", requested_size.w());
xml.attribute("height", requested_size.h());
});
}
});
});
}
void Floating_window_layouter::Main::generate_focus_model()
{
Reporter::Xml_generator xml(focus_reporter, [&] ()
{
xml.node("window", [&] () {
xml.attribute("id", _user_state.focused_window_id().value);
});
});
}
/**
* Determine window element that corresponds to hover model
*/
static Floating_window_layouter::Window::Element
element_from_hover_model(Genode::Xml_node hover_window_xml)
{
typedef Floating_window_layouter::Window::Element::Type Type;
bool const left_sizer = hover_window_xml.has_sub_node("left_sizer"),
right_sizer = hover_window_xml.has_sub_node("right_sizer"),
top_sizer = hover_window_xml.has_sub_node("top_sizer"),
bottom_sizer = hover_window_xml.has_sub_node("bottom_sizer");
if (left_sizer && top_sizer) return Type::TOP_LEFT;
if (left_sizer && bottom_sizer) return Type::BOTTOM_LEFT;
if (left_sizer) return Type::LEFT;
if (right_sizer && top_sizer) return Type::TOP_RIGHT;
if (right_sizer && bottom_sizer) return Type::BOTTOM_RIGHT;
if (right_sizer) return Type::RIGHT;
if (top_sizer) return Type::TOP;
if (bottom_sizer) return Type::BOTTOM;
if (hover_window_xml.has_sub_node("title")) return Type::TITLE;
if (hover_window_xml.has_sub_node("closer")) return Type::CLOSER;
if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER;
if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER;
return Type::UNDEFINED;
}
void Floating_window_layouter::Main::handle_window_list_update(unsigned)
{
window_list.update();
try {
import_window_list(Xml_node(window_list.local_addr<char>())); }
catch (...) {
PERR("Error while importing window list"); }
generate_window_layout_model();
}
void Floating_window_layouter::Main::_apply_focus_request()
{
try {
Xml_node node(focus_request.local_addr<char>());
Window::Label const label = node.attribute_value("label", Window::Label(""));
int const id = node.attribute_value("id", 0L);
/* don't apply the same focus request twice */
if (id == handled_focus_request_id)
return;
bool focus_redefined = false;
/*
* Move all windows that match the requested label to the front while
* maintaining their ordering.
*/
Window *at = nullptr;
for (Window *w = windows.first(); w; w = w->next()) {
if (!w->label_matches(label))
continue;
focus_redefined = true;
/*
* Move window to behind the previous window that we moved to
* front. If 'w' is the first window that matches the selector,
* move it to the front ('at' argument of 'insert' is 0).
*/
windows.remove(w);
windows.insert(w, at);
/*
* Bring top-most window to the front of nitpicker's global view
* stack and set the focus to the top-most window.
*/
if (at == nullptr) {
w->topped();
_user_state.focused_window_id(w->id());
generate_focus_model();
}
at = w;
}
if (focus_redefined)
handled_focus_request_id = id;
}
catch (...) {
PERR("Error while handling focus request"); }
}
void Floating_window_layouter::Main::handle_focus_request_update(unsigned)
{
focus_request.update();
_apply_focus_request();
generate_window_layout_model();
}
void Floating_window_layouter::Main::handle_hover_update(unsigned)
{
hover.update();
try {
Xml_node const hover_xml(hover.local_addr<char>());
Xml_node const hover_window_xml = hover_xml.sub_node("window");
_user_state.hover(attribute(hover_window_xml, "id", 0UL),
element_from_hover_model(hover_window_xml));
}
/*
* An exception may occur during the 'Xml_node' construction if the hover
* model is missing or malformed. Under this condition, we invalidate
* the hover state.
*/
catch (...) {
_user_state.reset_hover();
/*
* Don't generate a focus-model update here. In a situation where the
* pointer has moved over a native nitpicker view (outside the realm of
* the window manager), the hover model as generated by the decorator
* naturally becomes empty. If we posted a focus update, this would
* steal the focus away from the native nitpicker view.
*/
}
/* propagate changed hovering to the decorator */
generate_window_layout_model();
}
int main(int argc, char **argv)
{
static Genode::Signal_receiver sig_rec;
static Floating_window_layouter::Main application(sig_rec);
/* import initial state */
application.handle_window_list_update(0);
/* process incoming signals */
for (;;) {
using namespace Genode;
Signal sig = sig_rec.wait_for_signal();
Signal_dispatcher_base *dispatcher =
dynamic_cast<Signal_dispatcher_base *>(sig.context());
if (dispatcher)
dispatcher->dispatch(sig.num());
}
}