/*
* Copyright (C) 2010 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Authored by: Gord Allott
*/
#include "HudController.h"
#include
#include
#include
#include "unity-shared/AnimationUtils.h"
#include "unity-shared/ApplicationManager.h"
#include "unity-shared/WindowManager.h"
#include "unity-shared/PanelStyle.h"
#include "unity-shared/ThemeSettings.h"
#include "unity-shared/UBusMessages.h"
#include "unity-shared/UnitySettings.h"
#include "unity-shared/UScreen.h"
#include "MultiMonitor.h"
#include "config.h"
namespace unity
{
namespace hud
{
namespace
{
DECLARE_LOGGER(logger, "unity.hud.controller");
const unsigned FADE_DURATION = 90;
}
Controller::Controller(Controller::ViewCreator const& create_view,
Controller::WindowCreator const& create_window)
: launcher_locked_out(false)
, multiple_launchers(true)
, hud_service_("com.canonical.hud", "/com/canonical/hud")
, visible_(false)
, need_show_(false)
, view_(nullptr)
, monitor_index_(0)
, create_view_(create_view)
, create_window_(create_window)
, timeline_animator_(Settings::Instance().low_gfx() ? 0 : FADE_DURATION)
{
LOG_DEBUG(logger) << "hud startup";
// As a default, the create_view_ function should just create a view.
if (create_view == nullptr)
{
create_view_ = [] {
return new hud::View;
};
}
// As a default. the create_window_ function should just create a base window.
if (create_window_ == nullptr)
{
create_window_ = [this]() {
return new ResizingBaseWindow("Hud",
[this](nux::Geometry const& geo) {
if (view_)
return GetInputWindowGeometry();
return geo;
});
};
}
SetupWindow();
UScreen::GetDefault()->changed.connect(sigc::track_obj([this] (int, std::vector const&) { Relayout(true); }, *this));
ubus.RegisterInterest(UBUS_HUD_CLOSE_REQUEST, sigc::mem_fun(this, &Controller::OnExternalHideHud));
//!!FIXME!! - just hijacks the dash close request so we get some more requests than normal,
ubus.RegisterInterest(UBUS_OVERLAY_CLOSE_REQUEST, sigc::mem_fun(this, &Controller::OnExternalHideHud));
ubus.RegisterInterest(UBUS_OVERLAY_SHOWN, [this] (GVariant *data) {
unity::glib::String overlay_identity;
gboolean can_maximise = FALSE;
gint32 overlay_monitor = 0;
int width, height;
g_variant_get(data, UBUS_OVERLAY_FORMAT_STRING, &overlay_identity, &can_maximise, &overlay_monitor, &width, &height);
if (overlay_identity.Str() != "hud")
{
HideHud();
}
});
WindowManager& wm = WindowManager::Default();
wm.screen_ungrabbed.connect(sigc::mem_fun(this, &Controller::OnScreenUngrabbed));
wm.initiate_spread.connect(sigc::mem_fun(this, &Controller::HideHud));
wm.screen_viewport_switch_started.connect(sigc::mem_fun(this, &Controller::HideHud));
hud_service_.queries_updated.connect(sigc::mem_fun(this, &Controller::OnQueriesFinished));
timeline_animator_.updated.connect(sigc::mem_fun(this, &Controller::OnViewShowHideFrame));
Settings::Instance().dpi_changed.connect(sigc::mem_fun(this, &Controller::OnDPIChanged));
Settings::Instance().launcher_position.changed.connect(sigc::hide(sigc::bind(sigc::mem_fun(this, &Controller::Relayout), false)));
EnsureHud();
}
void Controller::SetupWindow()
{
// Since BaseWindow is a View it is initially unowned. This means that the first
// reference that is taken grabs ownership of the pointer. Since the smart pointer
// references it, it becomes the owner, so no need to adopt the pointer here.
window_ = create_window_();
window_->SetBackgroundColor(nux::Color(0.0f, 0.0f, 0.0f, 0.0f));
window_->SetConfigureNotifyCallback(&Controller::OnWindowConfigure, this);
window_->ShowWindow(false);
window_->SetOpacity(0.0f);
window_->mouse_down_outside_pointer_grab_area.connect(
sigc::mem_fun(this, &Controller::OnMouseDownOutsideWindow));
if (nux::GetWindowThread()->IsEmbeddedWindow())
{
/* FIXME - first time we load our windows there is a race that causes the
* input window not to actually get input, this side steps that by causing
* an input window show and hide before we really need it. */
WindowManager& wm = WindowManager::Default();
wm.SaveInputFocus();
window_->EnableInputWindow(true, "Hud", true, false);
window_->EnableInputWindow(false, "Hud", true, false);
wm.RestoreInputFocus();
}
}
void Controller::SetupHudView()
{
LOG_DEBUG(logger) << "SetupHudView called";
view_ = create_view_();
view_->scale = Settings::Instance().em(monitor_index_)->DPIScale();
layout_ = new nux::VLayout(NUX_TRACKER_LOCATION);
layout_->AddView(view_, 1, nux::MINOR_POSITION_START);
window_->SetLayout(layout_);
window_->UpdateInputWindowGeometry();
view_->mouse_down_outside_pointer_grab_area.connect(sigc::mem_fun(this, &Controller::OnMouseDownOutsideWindow));
LOG_DEBUG(logger) << "connecting to signals";
view_->search_changed.connect(sigc::mem_fun(this, &Controller::OnSearchChanged));
view_->search_activated.connect(sigc::mem_fun(this, &Controller::OnSearchActivated));
view_->query_activated.connect(sigc::mem_fun(this, &Controller::OnQueryActivated));
view_->query_selected.connect(sigc::mem_fun(this, &Controller::OnQuerySelected));
view_->layout_changed.connect(sigc::bind(sigc::mem_fun(this, &Controller::Relayout), false));
// Add to the debug introspection.
AddChild(view_);
}
int Controller::GetIdealMonitor()
{
int ideal_monitor;
if (window_->IsVisible())
ideal_monitor = monitor_index_;
else
ideal_monitor = UScreen::GetDefault()->GetMonitorWithMouse();
return ideal_monitor;
}
bool Controller::IsLockedToLauncher(int monitor)
{
if (launcher_locked_out && Settings::Instance().launcher_position() == LauncherPosition::LEFT)
{
int primary_monitor = UScreen::GetDefault()->GetPrimaryMonitor();
if (multiple_launchers || (!multiple_launchers && primary_monitor == monitor))
{
return true;
}
}
return false;
}
void Controller::EnsureHud()
{
if (!window_)
{
LOG_DEBUG(logger) << "Initializing Hud Window";
SetupWindow();
}
if (!view_)
{
LOG_DEBUG(logger) << "Initializing Hud View";
SetupHudView();
Relayout();
}
}
void Controller::SetIcon(std::string const& icon_name)
{
LOG_DEBUG(logger) << "setting icon to - " << icon_name;
int launcher_size = unity::Settings::Instance().LauncherSize(monitor_index_);
if (view_)
{
double scale = view_->scale();
int tsize = tile_size().CP(scale);
view_->SetIcon(icon_name, tsize, icon_size().CP(scale), launcher_size - tsize);
}
ubus.SendMessage(UBUS_HUD_ICON_CHANGED, g_variant_new_string(icon_name.c_str()));
}
nux::BaseWindow* Controller::window() const
{
return window_.GetPointer();
}
nux::ObjectPtr Controller::HudView() const
{
return nux::ObjectPtr(view_);
}
// We update the @geo that's sent in with our desired width and height
void Controller::OnWindowConfigure(int window_width, int window_height,
nux::Geometry& geo, void* data)
{
Controller* self = static_cast(data);
geo = self->GetIdealWindowGeometry();
}
nux::Geometry Controller::GetIdealWindowGeometry()
{
int ideal_monitor = GetIdealMonitor();
auto monitor_geo = UScreen::GetDefault()->GetMonitorGeometry(ideal_monitor);
panel::Style &panel_style = panel::Style::Instance();
int panel_height = panel_style.PanelHeight(ideal_monitor);
// We want to cover as much of the screen as possible to grab any mouse events
// outside of our window
nux::Geometry geo(monitor_geo.x,
monitor_geo.y + panel_height,
monitor_geo.width,
monitor_geo.height - panel_height);
if (IsLockedToLauncher(ideal_monitor))
{
int launcher_width = unity::Settings::Instance().LauncherSize(ideal_monitor);
geo.x += launcher_width;
geo.width -= launcher_width;
}
return geo;
}
void Controller::Relayout(bool check_monitor)
{
EnsureHud();
if (check_monitor)
monitor_index_ = CLAMP(GetIdealMonitor(), 0, static_cast(UScreen::GetDefault()->GetMonitors().size()-1));
nux::Geometry const& geo = GetIdealWindowGeometry();
view_->QueueDraw();
window_->SetGeometry(geo);
panel::Style &panel_style = panel::Style::Instance();
int horizontal_offset = 0;
if (Settings::Instance().launcher_position() == LauncherPosition::LEFT)
horizontal_offset = unity::Settings::Instance().LauncherSize(monitor_index_);
view_->ShowEmbeddedIcon(!IsLockedToLauncher(monitor_index_));
view_->SetMonitorOffset(horizontal_offset, panel_style.PanelHeight(monitor_index_));
}
void Controller::OnMouseDownOutsideWindow(int x, int y,
unsigned long bflags, unsigned long kflags)
{
LOG_DEBUG(logger) << "OnMouseDownOutsideWindow called";
HideHud();
}
void Controller::OnScreenUngrabbed()
{
LOG_DEBUG(logger) << "OnScreenUngrabbed called";
if (need_show_)
{
nux::GetWindowCompositor().SetKeyFocusArea(view_->default_focus());
window_->PushToFront();
window_->SetInputFocus();
EnsureHud();
ShowHud();
}
}
void Controller::OnExternalShowHud(GVariant* variant)
{
EnsureHud();
visible_ ? HideHud() : ShowHud();
}
void Controller::OnExternalHideHud(GVariant* variant)
{
LOG_DEBUG(logger) << "External Hiding the hud";
HideHud();
}
void Controller::ShowHideHud()
{
EnsureHud();
visible_ ? HideHud() : ShowHud();
}
void Controller::ReFocusKeyInput()
{
if (visible_)
{
window_->PushToFront();
window_->SetInputFocus();
}
}
bool Controller::IsVisible()
{
return visible_;
}
void Controller::ShowHud()
{
WindowManager& wm = WindowManager::Default();
LOG_DEBUG(logger) << "Showing the hud";
EnsureHud();
if (visible_ || wm.IsExpoActive() || wm.IsScaleActive())
return;
if (wm.IsScreenGrabbed())
{
need_show_ = true;
return;
}
unsigned int ideal_monitor = GetIdealMonitor();
if (ideal_monitor != monitor_index_)
{
Relayout();
monitor_index_ = ideal_monitor;
view_->scale = Settings::Instance().em(monitor_index_)->DPIScale();
}
view_->ShowEmbeddedIcon(!IsLockedToLauncher(monitor_index_));
view_->AboutToShow();
ApplicationManager& app_manager = ApplicationManager::Default();
ApplicationPtr active_application;
ApplicationWindowPtr active_window = app_manager.GetActiveWindow();
if (active_window)
active_application = active_window->application();
if (active_application)
{
focused_app_icon_ = active_application->icon();
}
else
{
focused_app_icon_ = theme::Settings::Get()->ThemedFilePath("launcher_bfb", {PKGDATADIR});
}
wm.SaveInputFocus();
LOG_DEBUG(logger) << "Taking application icon: " << focused_app_icon_;
SetIcon(focused_app_icon_);
FocusWindow();
view_->ResetToDefault();
need_show_ = true;
visible_ = true;
StartShowHideTimeline();
// hide the launcher
ubus.SendMessage(UBUS_LAUNCHER_LOCK_HIDE, glib::Variant(true));
auto const& view_content_geometry = view_->GetContentGeometry();
GVariant* info = g_variant_new(UBUS_OVERLAY_FORMAT_STRING, "hud", FALSE, monitor_index_,
view_content_geometry.width, view_content_geometry.height);
ubus.SendMessage(UBUS_OVERLAY_SHOWN, info);
nux::GetWindowCompositor().SetKeyFocusArea(view_->default_focus());
window_->SetEnterFocusInputArea(view_->default_focus());
}
void Controller::FocusWindow()
{
window_->ShowWindow(true);
window_->PushToFront();
if (nux::GetWindowThread()->IsEmbeddedWindow())
{
window_->EnableInputWindow(true, "Hud", true, false);
window_->UpdateInputWindowGeometry();
}
window_->SetInputFocus();
window_->QueueDraw();
}
void Controller::HideHud()
{
LOG_DEBUG (logger) << "hiding the hud";
if (!visible_)
return;
need_show_ = false;
EnsureHud();
view_->AboutToHide();
view_->ShowEmbeddedIcon(false);
window_->CaptureMouseDownAnyWhereElse(false);
window_->EnableInputWindow(false, "Hud", true, false);
visible_ = false;
auto& wc = nux::GetWindowCompositor();
auto *key_focus_area = wc.GetKeyFocusArea();
if (key_focus_area && key_focus_area->IsChildOf(view_))
wc.SetKeyFocusArea(nullptr, nux::KEY_NAV_NONE);
WindowManager::Default().RestoreInputFocus();
StartShowHideTimeline();
hud_service_.CloseQuery();
//unhide the launcher
ubus.SendMessage(UBUS_LAUNCHER_LOCK_HIDE, glib::Variant(false));
auto const& view_content_geometry = view_->GetContentGeometry();
GVariant* info = g_variant_new(UBUS_OVERLAY_FORMAT_STRING, "hud", FALSE, monitor_index_,
view_content_geometry.width, view_content_geometry.height);
ubus.SendMessage(UBUS_OVERLAY_HIDDEN, info);
}
void Controller::StartShowHideTimeline()
{
EnsureHud();
animation::StartOrReverseIf(timeline_animator_, visible_);
}
void Controller::OnViewShowHideFrame(double opacity)
{
window_->SetOpacity(opacity);
if (opacity == 0.0f && !visible_)
{
window_->ShowWindow(false);
}
else if (opacity == 1.0f && visible_)
{
// ensure the text entry is focused
nux::GetWindowCompositor().SetKeyFocusArea(view_->default_focus());
}
}
void Controller::OnActivateRequest(GVariant* variant)
{
EnsureHud();
ShowHud();
}
void Controller::OnSearchChanged(std::string search_string)
{
// we're using live_search_reached, so this is called 40ms after the text
// is input in the search bar
LOG_DEBUG(logger) << "Search Changed";
last_search_ = search_string;
hud_service_.RequestQuery(last_search_);
}
void Controller::OnSearchActivated(std::string search_string)
{
unsigned int timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp;
hud_service_.ExecuteQueryBySearch(search_string, timestamp);
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
}
void Controller::OnQueryActivated(Query::Ptr query)
{
LOG_DEBUG(logger) << "Activating query, " << query->formatted_text;
unsigned int timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp;
hud_service_.ExecuteQuery(query, timestamp);
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
}
void Controller::OnQuerySelected(Query::Ptr query)
{
LOG_DEBUG(logger) << "Selected query, " << query->formatted_text;
SetIcon(query->icon_name);
}
void Controller::OnQueriesFinished(Hud::Queries queries)
{
view_->SetQueries(queries);
std::string icon_name = focused_app_icon_;
for (auto query = queries.begin(); query != queries.end(); query++)
{
if (!(*query)->icon_name.empty())
{
icon_name = (*query)->icon_name;
break;
}
}
SetIcon(icon_name);
view_->SearchFinished();
}
void Controller::OnDPIChanged()
{
if (view_)
view_->scale = Settings::Instance().em(monitor_index_)->DPIScale();
}
// Introspectable
std::string Controller::GetName() const
{
return "HudController";
}
void Controller::AddProperties(debug::IntrospectionData& introspection)
{
introspection
.add(window_ ? window_->GetGeometry() : nux::Geometry())
.add("ideal_monitor", GetIdealMonitor())
.add("visible", visible_)
.add("hud_monitor", monitor_index_)
.add("locked_to_launcher", IsLockedToLauncher(monitor_index_));
}
nux::Geometry Controller::GetInputWindowGeometry()
{
EnsureHud();
nux::Geometry const& window_geo(window_->GetGeometry());
nux::Geometry const& view_content_geo(view_->GetContentGeometry());
return nux::Geometry(window_geo.x, window_geo.y, view_content_geo.width, view_content_geo.height);
}
}
}