613 lines
15 KiB
C++
613 lines
15 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>
|
|
#include <os/config.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" };
|
|
|
|
|
|
/**
|
|
* Respond to decorator-margins information reported by the decorator
|
|
*/
|
|
Attached_rom_dataspace decorator_margins { "decorator_margins" };
|
|
|
|
void handle_decorator_margins_update(unsigned)
|
|
{
|
|
decorator_margins.update();
|
|
|
|
/* respond to change by adapting the maximized window geometry */
|
|
handle_mode_change(0);
|
|
}
|
|
|
|
Signal_dispatcher<Main> decorator_margins_dispatcher = {
|
|
sig_rec, *this, &Main::handle_decorator_margins_update };
|
|
|
|
|
|
/**
|
|
* 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(), Genode::config()->xml_node());
|
|
}
|
|
|
|
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();
|
|
|
|
/* read decorator margins from the decorator's report */
|
|
unsigned top = 0, bottom = 0, left = 0, right = 0;
|
|
try {
|
|
Xml_node const margins_xml(decorator_margins.local_addr<char>());
|
|
Xml_node const floating_xml = margins_xml.sub_node("floating");
|
|
|
|
top = attribute(floating_xml, "top", 0UL);
|
|
bottom = attribute(floating_xml, "bottom", 0UL);
|
|
left = attribute(floating_xml, "left", 0UL);
|
|
right = attribute(floating_xml, "right", 0UL);
|
|
|
|
} catch (...) { };
|
|
|
|
maximized_window_geometry = Rect(Point(left, top),
|
|
Area(mode.width() - left - right,
|
|
mode.height() - top - 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);
|
|
decorator_margins.sigh(decorator_margins_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());
|
|
}
|
|
}
|