// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
/*
* Copyright (C) 2010-2012 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: Jason Smith
* Tim Penhey
* Marco Trevisan
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include "LauncherOptions.h"
#include "ApplicationLauncherIcon.h"
#include "DesktopLauncherIcon.h"
#include "VolumeLauncherIcon.h"
#include "FavoriteStore.h"
#include "FileManagerLauncherIcon.h"
#include "LauncherController.h"
#include "LauncherControllerPrivate.h"
#include "LauncherControllerPrivateImpl.h"
#include "SoftwareCenterLauncherIcon.h"
#include "ExpoLauncherIcon.h"
#include "TrashLauncherIcon.h"
#include "unity-shared/AppStreamApplication.h"
#include "unity-shared/IconRenderer.h"
#include "unity-shared/UScreen.h"
#include "unity-shared/UBusMessages.h"
#include "unity-shared/TimeUtil.h"
#include "unity-shared/PanelStyle.h"
#include "unity-shared/UnitySettings.h"
namespace unity
{
namespace launcher
{
DECLARE_LOGGER(logger, "unity.launcher.controller");
namespace
{
const std::string DBUS_NAME = "com.canonical.Unity.Launcher";
const std::string DBUS_PATH = "/com/canonical/Unity/Launcher";
const std::string DBUS_INTROSPECTION =
""
" "
""
" "
" "
" "
" "
""
" "
" "
" "
" "
""
" "
"";
}
namespace local
{
namespace
{
const int launcher_minimum_show_duration = 1250;
const int shortcuts_show_delay = 750;
const std::string KEYPRESS_TIMEOUT = "keypress-timeout";
const std::string LABELS_TIMEOUT = "label-show-timeout";
const std::string HIDE_TIMEOUT = "hide-timeout";
const std::string SOFTWARE_CENTER_AGENT = "software-center-agent";
const std::string RUNNING_APPS_URI = FavoriteStore::URI_PREFIX_UNITY + "running-apps";
const std::string DEVICES_URI = FavoriteStore::URI_PREFIX_UNITY + "devices";
}
std::string CreateAppUriNameFromDesktopPath(const std::string &desktop_path)
{
if (desktop_path.empty())
return "";
return FavoriteStore::URI_PREFIX_APP + DesktopUtilities::GetDesktopID(desktop_path);
}
}
Controller::Impl::Impl(Controller* parent, XdndManager::Ptr const& xdnd_manager, ui::EdgeBarrierController::Ptr const& edge_barriers)
: parent_(parent)
, model_(std::make_shared())
, xdnd_manager_(xdnd_manager)
, device_section_(std::make_shared())
, bfb_icon_(new BFBLauncherIcon())
, hud_icon_(new HudLauncherIcon())
, expo_icon_(new ExpoLauncherIcon())
, desktop_icon_(new DesktopLauncherIcon())
, edge_barriers_(edge_barriers)
, sort_priority_(AbstractLauncherIcon::DefaultPriority(AbstractLauncherIcon::IconType::APPLICATION))
, launcher_open(false)
, launcher_keynav(false)
, launcher_grabbed(false)
, keynav_restore_window_(true)
, launcher_key_press_time_(0)
, dbus_server_(DBUS_NAME)
{
#ifdef USE_X11
edge_barriers_->options = parent_->options();
#endif
UScreen* uscreen = UScreen::GetDefault();
EnsureLaunchers(uscreen->GetPrimaryMonitor(), uscreen->GetMonitors());
uscreen->changed.connect(sigc::mem_fun(this, &Controller::Impl::EnsureLaunchers));
SetupIcons();
remote_model_.entry_added.connect(sigc::mem_fun(this, &Impl::OnLauncherEntryRemoteAdded));
remote_model_.entry_removed.connect(sigc::mem_fun(this, &Impl::OnLauncherEntryRemoteRemoved));
auto hide_mode = parent_->options()->hide_mode();
bfb_icon_->SetHideMode(hide_mode);
RegisterIcon(AbstractLauncherIcon::Ptr(bfb_icon_));
hud_icon_->SetHideMode(hide_mode);
RegisterIcon(AbstractLauncherIcon::Ptr(hud_icon_));
TrashLauncherIcon* trash = new TrashLauncherIcon();
RegisterIcon(AbstractLauncherIcon::Ptr(trash));
parent_->options()->hide_mode.changed.connect([this] (LauncherHideMode mode) {
bfb_icon_->SetHideMode(mode);
hud_icon_->SetHideMode(mode);
});
parent_->multiple_launchers.changed.connect([this] (bool value) {
UScreen* uscreen = UScreen::GetDefault();
auto monitors = uscreen->GetMonitors();
int primary = uscreen->GetPrimaryMonitor();
EnsureLaunchers(primary, monitors);
parent_->options()->show_for_all = !value;
hud_icon_->SetSingleLauncher(!value, primary);
});
WindowManager& wm = WindowManager::Default();
wm.window_focus_changed.connect(sigc::mem_fun(this, &Controller::Impl::OnWindowFocusChanged));
wm.viewport_layout_changed.connect(sigc::track_obj([this] (int w, int h) { UpdateNumWorkspaces(w * h); }, *this));
wm.average_color.changed.connect(sigc::track_obj([this] (nux::Color const& color) {
parent_->options()->background_color = color;
}, *this));
ubus.RegisterInterest(UBUS_QUICKLIST_END_KEY_NAV, [this](GVariant * args) {
reactivate_index = model_->SelectionIndex();
parent_->KeyNavGrab();
keynav_restore_window_ = true;
model_->SetSelection(reactivate_index);
AbstractLauncherIcon::Ptr const& selected = model_->Selection();
if (selected)
{
ubus.SendMessage(UBUS_LAUNCHER_SELECTION_CHANGED, glib::Variant(selected->tooltip_text()));
}
});
ubus.RegisterInterest(UBUS_LAUNCHER_NEXT_KEY_NAV, [this] (GVariant*) {
parent_->KeyNavNext();
});
ubus.RegisterInterest(UBUS_LAUNCHER_PREV_KEY_NAV, [this] (GVariant*) {
parent_->KeyNavPrevious();
});
ubus.RegisterInterest(UBUS_LAUNCHER_OPEN_QUICKLIST, [this] (GVariant*) {
OpenQuicklist();
});
parent_->AddChild(model_.get());
xdnd_manager_->dnd_started.connect(sigc::mem_fun(this, &Impl::OnDndStarted));
xdnd_manager_->dnd_finished.connect(sigc::mem_fun(this, &Impl::OnDndFinished));
xdnd_manager_->monitor_changed.connect(sigc::mem_fun(this, &Impl::OnDndMonitorChanged));
dbus_server_.AddObjects(DBUS_INTROSPECTION, DBUS_PATH);
for (auto const& obj : dbus_server_.GetObjects())
obj->SetMethodsCallsHandler(sigc::mem_fun(this, &Impl::OnDBusMethodCall));
}
Controller::Impl::~Impl()
{
// Since the launchers are in a window which adds a reference to the
// launcher, we need to make sure the base windows are unreferenced
// otherwise the launchers never die.
for (auto const& launcher_ptr : launchers)
{
if (launcher_ptr)
launcher_ptr->GetParent()->UnReference();
}
}
void Controller::Impl::EnsureLaunchers(int primary, std::vector const& monitors)
{
unsigned int num_monitors = monitors.size();
unsigned int num_launchers = parent_->multiple_launchers ? num_monitors : 1;
unsigned int launchers_size = launchers.size();
unsigned int last_launcher = 0;
// Reset the icon centers: only the used icon centers must contain valid values
for (auto const& icon : *model_)
icon->ResetCenters();
for (unsigned int i = 0; i < num_launchers; ++i, ++last_launcher)
{
if (i >= launchers_size)
{
launchers.push_back(nux::ObjectPtr(CreateLauncher()));
}
else if (!launchers[i])
{
launchers[i] = nux::ObjectPtr(CreateLauncher());
}
int monitor = (num_launchers == 1 && num_monitors > 1) ? primary : i;
if (launchers[i]->monitor() != monitor)
{
#ifdef USE_X11
edge_barriers_->RemoveVerticalSubscriber(launchers[i].GetPointer(), launchers[i]->monitor);
#endif
launchers[i]->monitor = monitor;
}
else
{
launchers[i]->monitor.changed(monitor);
}
#ifdef USE_X11
edge_barriers_->AddVerticalSubscriber(launchers[i].GetPointer(), launchers[i]->monitor);
#endif
}
for (unsigned int i = last_launcher; i < launchers_size; ++i)
{
auto const& launcher = launchers[i];
if (launcher)
{
parent_->RemoveChild(launcher.GetPointer());
launcher->GetParent()->UnReference();
#ifdef USE_X11
edge_barriers_->RemoveVerticalSubscriber(launcher.GetPointer(), launcher->monitor);
#endif
}
}
launcher_ = launchers[0];
launchers.resize(num_launchers);
}
void Controller::Impl::OnWindowFocusChanged(guint32 xid)
{
static bool keynav_first_focus = false;
if (parent_->IsOverlayOpen() || CurrentLauncher()->GetParent()->GetInputWindowId() == xid)
keynav_first_focus = false;
if (keynav_first_focus)
{
keynav_first_focus = false;
keynav_restore_window_ = false;
parent_->KeyNavTerminate(false);
}
else if (launcher_keynav)
{
keynav_first_focus = true;
}
}
void Controller::Impl::OnDndStarted(std::string const& data, int monitor)
{
if (parent_->multiple_launchers)
{
launchers[monitor]->DndStarted(data);
}
else
{
launcher_->DndStarted(data);
}
}
void Controller::Impl::OnDndFinished()
{
if (parent_->multiple_launchers)
{
if (xdnd_manager_->Monitor() >= 0)
launchers[xdnd_manager_->Monitor()]->DndFinished();
}
else
{
launcher_->DndFinished();
}
}
void Controller::Impl::OnDndMonitorChanged(std::string const& data, int old_monitor, int new_monitor)
{
if (parent_->multiple_launchers)
{
if (old_monitor >= 0)
launchers[old_monitor]->UnsetDndQuirk();
launchers[new_monitor]->DndStarted(data);
}
}
Launcher* Controller::Impl::CreateLauncher()
{
auto* launcher_window = new MockableBaseWindow(TEXT("LauncherWindow"));
Launcher* launcher = new Launcher(launcher_window);
launcher->options = parent_->options();
launcher->SetModel(model_);
nux::HLayout* layout = new nux::HLayout(NUX_TRACKER_LOCATION);
layout->AddView(launcher, 1);
layout->SetContentDistribution(nux::MAJOR_POSITION_START);
layout->SetVerticalExternalMargin(0);
layout->SetHorizontalExternalMargin(0);
launcher_window->SetLayout(layout);
launcher_window->SetBackgroundColor(nux::color::Transparent);
launcher_window->ShowWindow(true);
if (nux::GetWindowThread()->IsEmbeddedWindow())
launcher_window->EnableInputWindow(true, launcher::window_title, false, false);
launcher_window->InputWindowEnableStruts(parent_->options()->hide_mode == LAUNCHER_HIDE_NEVER);
launcher_window->SetEnterFocusInputArea(launcher);
launcher->add_request.connect(sigc::mem_fun(this, &Impl::OnLauncherAddRequest));
launcher->remove_request.connect(sigc::mem_fun(this, &Impl::OnLauncherRemoveRequest));
parent_->AddChild(launcher);
return launcher;
}
ApplicationLauncherIcon* Controller::Impl::CreateAppLauncherIcon(ApplicationPtr const& app)
{
auto const& desktop_file = app->desktop_file();
if (boost::algorithm::ends_with(desktop_file, "org.gnome.Nautilus.desktop") ||
boost::algorithm::ends_with(desktop_file, "nautilus.desktop") ||
boost::algorithm::ends_with(desktop_file, "nautilus-folder-handler.desktop") ||
boost::algorithm::ends_with(desktop_file, "nautilus-home.desktop"))
{
return new FileManagerLauncherIcon(app, device_section_);
}
return new ApplicationLauncherIcon(app);
}
void Controller::Impl::OnLauncherAddRequest(std::string const& icon_uri, AbstractLauncherIcon::Ptr const& icon_before)
{
std::string app_uri;
if (icon_uri.find(FavoriteStore::URI_PREFIX_FILE) == 0)
{
auto const& desktop_path = icon_uri.substr(FavoriteStore::URI_PREFIX_FILE.length());
app_uri = local::CreateAppUriNameFromDesktopPath(desktop_path);
}
auto const& icon = GetIconByUri(app_uri.empty() ? icon_uri : app_uri);
if (icon)
{
model_->ReorderAfter(icon, icon_before);
icon->Stick(true);
}
else
{
if (icon_before)
RegisterIcon(CreateFavoriteIcon(icon_uri, true), icon_before->SortPriority());
else
RegisterIcon(CreateFavoriteIcon(icon_uri, true));
SaveIconsOrder();
}
}
void Controller::Impl::AddFavoriteKeepingOldPosition(FavoriteList& icons, std::string const& icon_uri) const
{
auto const& favorites = FavoriteStore::Instance().GetFavorites();
auto it = std::find(favorites.rbegin(), favorites.rend(), icon_uri);
FavoriteList::reverse_iterator icons_it = icons.rbegin();
while (it != favorites.rend())
{
icons_it = std::find(icons.rbegin(), icons.rend(), *it);
if (icons_it != icons.rend())
break;
++it;
}
icons.insert(icons_it.base(), icon_uri);
}
void Controller::Impl::SaveIconsOrder()
{
FavoriteList icons;
bool found_first_running_app = false;
bool found_first_device = false;
for (auto const& icon : *model_)
{
if (!icon->IsSticky())
{
if (!icon->IsVisible())
continue;
if (!found_first_running_app && icon->GetIconType() == AbstractLauncherIcon::IconType::APPLICATION)
{
found_first_running_app = true;
icons.push_back(local::RUNNING_APPS_URI);
}
if (!found_first_device && icon->GetIconType() == AbstractLauncherIcon::IconType::DEVICE)
{
found_first_device = true;
icons.push_back(local::DEVICES_URI);
}
continue;
}
std::string const& remote_uri = icon->RemoteUri();
if (!remote_uri.empty())
icons.push_back(remote_uri);
}
if (!found_first_running_app)
AddFavoriteKeepingOldPosition(icons, local::RUNNING_APPS_URI);
if (!found_first_device)
AddFavoriteKeepingOldPosition(icons, local::DEVICES_URI);
FavoriteStore::Instance().SetFavorites(icons);
}
void
Controller::Impl::OnLauncherUpdateIconStickyState(std::string const& icon_uri, bool sticky)
{
if (icon_uri.empty())
return;
std::string target_uri = icon_uri;
if (icon_uri.find(FavoriteStore::URI_PREFIX_FILE) == 0)
{
auto const& desktop_path =
icon_uri.substr(FavoriteStore::URI_PREFIX_FILE.length());
// app uri instead
target_uri = local::CreateAppUriNameFromDesktopPath(desktop_path);
}
auto const& existing_icon_entry =
GetIconByUri(target_uri);
if (existing_icon_entry)
{
// use the backgroung mechanism of model updates & propagation
bool should_update = (existing_icon_entry->IsSticky() != sticky);
if (should_update)
{
if (sticky)
{
existing_icon_entry->Stick(true);
}
else
{
existing_icon_entry->UnStick();
}
}
}
else
{
FavoriteStore& favorite_store = FavoriteStore::Instance();
bool should_update = (favorite_store.IsFavorite(target_uri) != sticky);
if (should_update)
{
if (sticky)
{
auto prio = GetLastIconPriority("", true);
RegisterIcon(CreateFavoriteIcon(target_uri, true), prio);
SaveIconsOrder();
}
else
{
favorite_store.RemoveFavorite(target_uri);
}
}
}
}
void
Controller::Impl::OnLauncherAddRequestSpecial(std::string const& appstream_app_id,
std::string const& aptdaemon_trans_id)
{
// Check if desktop file was supplied
if (appstream_app_id.empty())
return;
auto const& icon = std::find_if(model_->begin(), model_->end(),
[&appstream_app_id](AbstractLauncherIcon::Ptr const& i) { return (i->DesktopFile() == appstream_app_id); });
if (icon != model_->end())
return;
auto const& result = CreateSCLauncherIcon(appstream_app_id, aptdaemon_trans_id);
if (result)
{
RegisterIcon(result, GetLastIconPriority("", true));
result->SetQuirk(AbstractLauncherIcon::Quirk::VISIBLE, true);
}
}
void Controller::Impl::SortAndUpdate()
{
unsigned shortcut = 1;
for (auto const& icon : model_->GetSublist())
{
if (shortcut <= 10 && icon->IsVisible())
{
icon->SetShortcut(std::to_string(shortcut % 10)[0]);
++shortcut;
}
else if (isdigit(icon->GetShortcut()))
{
// reset shortcut
icon->SetShortcut(0);
}
}
}
void Controller::Impl::OnIconRemoved(AbstractLauncherIcon::Ptr const& icon)
{
SortAndUpdate();
}
void Controller::Impl::OnLauncherRemoveRequest(AbstractLauncherIcon::Ptr const& icon)
{
icon->AboutToRemove();
}
void Controller::Impl::OnLauncherEntryRemoteAdded(LauncherEntryRemote::Ptr const& entry)
{
if (entry->AppUri().empty())
return;
auto const& apps_icons = model_->GetSublist();
auto const& icon = std::find_if(apps_icons.begin(), apps_icons.end(),
[&entry](AbstractLauncherIcon::Ptr const& i) { return (i->RemoteUri() == entry->AppUri()); });
if (icon != apps_icons.end())
(*icon)->InsertEntryRemote(entry);
}
void Controller::Impl::OnLauncherEntryRemoteRemoved(LauncherEntryRemote::Ptr const& entry)
{
for (auto const& icon : *model_)
icon->RemoveEntryRemote(entry);
}
void Controller::Impl::OnFavoriteStoreFavoriteAdded(std::string const& entry, std::string const& pos, bool before)
{
if (entry == local::RUNNING_APPS_URI || entry == local::DEVICES_URI)
{
// Since the running apps and the devices are always shown, when added to
// the model, we only have to re-order them
ResetIconPriorities();
return;
}
AbstractLauncherIcon::Ptr other = *(model_->begin());
if (!pos.empty())
{
for (auto const& it : *model_)
{
if (it->IsVisible() && pos == it->RemoteUri())
other = it;
}
}
AbstractLauncherIcon::Ptr const& fav = GetIconByUri(entry);
if (fav)
{
fav->Stick(false);
if (before)
model_->ReorderBefore(fav, other, false);
else
model_->ReorderAfter(fav, other);
}
else
{
AbstractLauncherIcon::Ptr const& result = CreateFavoriteIcon(entry);
RegisterIcon(result);
if (before)
model_->ReorderBefore(result, other, false);
else
model_->ReorderAfter(result, other);
}
SortAndUpdate();
}
void Controller::Impl::OnFavoriteStoreFavoriteRemoved(std::string const& entry)
{
if (entry == local::RUNNING_APPS_URI || entry == local::DEVICES_URI)
{
// Since the running apps and the devices are always shown, when added to
// the model, we only have to re-order them
ResetIconPriorities();
SaveIconsOrder();
return;
}
auto const& icon = GetIconByUri(entry);
if (icon)
{
icon->UnStick();
// When devices are removed from favorites, they should be re-ordered (not removed)
if (icon->GetIconType() == AbstractLauncherIcon::IconType::DEVICE)
ResetIconPriorities();
}
}
void Controller::Impl::ResetIconPriorities()
{
FavoriteList const& favs = FavoriteStore::Instance().GetFavorites();
auto const& apps_icons = model_->GetSublist();
auto const& volumes_icons = model_->GetSublist();
bool running_apps_found = false;
bool volumes_found = false;
for (auto const& fav : favs)
{
if (fav == local::RUNNING_APPS_URI)
{
for (auto const& ico : apps_icons)
{
if (!ico->IsSticky())
ico->SetSortPriority(++sort_priority_);
}
running_apps_found = true;
continue;
}
else if (fav == local::DEVICES_URI)
{
for (auto const& ico : volumes_icons)
{
if (!ico->IsSticky())
ico->SetSortPriority(++sort_priority_);
}
volumes_found = true;
continue;
}
auto const& icon = GetIconByUri(fav);
if (icon)
icon->SetSortPriority(++sort_priority_);
}
if (!running_apps_found)
{
for (auto const& ico : apps_icons)
{
if (!ico->IsSticky())
ico->SetSortPriority(++sort_priority_);
}
}
if (!volumes_found)
{
for (auto const& ico : volumes_icons)
{
if (!ico->IsSticky())
ico->SetSortPriority(++sort_priority_);
}
}
model_->Sort();
}
void Controller::Impl::UpdateNumWorkspaces(int workspaces)
{
bool visible = expo_icon_->IsVisible();
bool wp_enabled = (workspaces > 1);
if (wp_enabled && !visible)
{
if (FavoriteStore::Instance().IsFavorite(expo_icon_->RemoteUri()))
{
expo_icon_->SetQuirk(AbstractLauncherIcon::Quirk::VISIBLE, true);
}
}
else if (!wp_enabled && visible)
{
expo_icon_->SetQuirk(AbstractLauncherIcon::Quirk::VISIBLE, false);
}
}
void Controller::Impl::RegisterIcon(AbstractLauncherIcon::Ptr const& icon, int priority)
{
if (!icon)
return;
std::string const& icon_uri = icon->RemoteUri();
if (model_->IconIndex(icon) >= 0)
{
if (!icon_uri.empty())
{
LOG_ERROR(logger) << "Impossible to add icon '" << icon_uri
<< "': it's already on the launcher!";
}
return;
}
if (priority != std::numeric_limits::min())
icon->SetSortPriority(priority);
icon->position_saved.connect([this] {
// These calls must be done in order: first we save the new sticky icons
// then we re-order the model so that there won't be two icons with the same
// priority
SaveIconsOrder();
ResetIconPriorities();
});
auto uri_ptr = std::make_shared(icon_uri);
icon->position_forgot.connect([this, uri_ptr] {
FavoriteStore::Instance().RemoveFavorite(*uri_ptr);
});
icon->uri_changed.connect([this, uri_ptr] (std::string const& new_uri) {
*uri_ptr = new_uri;
});
model_->AddIcon(icon);
if (icon->GetIconType() == AbstractLauncherIcon::IconType::APPLICATION)
{
icon->visibility_changed.connect(sigc::hide(sigc::mem_fun(this, &Impl::SortAndUpdate)));
SortAndUpdate();
}
std::string const& path = icon->DesktopFile();
if (!path.empty())
{
LauncherEntryRemote::Ptr const& entry = remote_model_.LookupByDesktopFile(path);
if (entry)
icon->InsertEntryRemote(entry);
}
}
void Controller::Impl::OnApplicationStarted(ApplicationPtr const& app)
{
if (app->sticky() || app->seen())
return;
AbstractLauncherIcon::Ptr icon(CreateAppLauncherIcon(app));
RegisterIcon(icon, GetLastIconPriority(local::RUNNING_APPS_URI));
}
void Controller::Impl::OnDeviceIconAdded(AbstractLauncherIcon::Ptr const& icon)
{
RegisterIcon(icon, GetLastIconPriority(local::DEVICES_URI));
}
AbstractLauncherIcon::Ptr Controller::Impl::CreateFavoriteIcon(std::string const& icon_uri, bool emit_signal)
{
AbstractLauncherIcon::Ptr result;
if (!FavoriteStore::IsValidFavoriteUri(icon_uri))
{
LOG_WARNING(logger) << "Ignoring favorite '" << icon_uri << "'.";
return result;
}
std::string desktop_id;
if (icon_uri.find(FavoriteStore::URI_PREFIX_APP) == 0)
{
desktop_id = icon_uri.substr(FavoriteStore::URI_PREFIX_APP.size());
}
else if (icon_uri.find(FavoriteStore::URI_PREFIX_FILE) == 0)
{
desktop_id = icon_uri.substr(FavoriteStore::URI_PREFIX_FILE.size());
}
if (!desktop_id.empty())
{
std::string const& desktop_path = DesktopUtilities::GetDesktopPathById(desktop_id);
ApplicationPtr app = ApplicationManager::Default().GetApplicationForDesktopFile(desktop_path);
if (!app || app->seen())
return result;
result = AbstractLauncherIcon::Ptr(CreateAppLauncherIcon(app));
}
else if (icon_uri.find(FavoriteStore::URI_PREFIX_DEVICE) == 0)
{
auto const& devices = device_section_->GetIcons();
auto const& icon = std::find_if(devices.begin(), devices.end(),
[&icon_uri](AbstractLauncherIcon::Ptr const& i) { return (i->RemoteUri() == icon_uri); });
if (icon == devices.end())
{
// Using an idle to remove the favorite, not to erase while iterating
sources_.AddIdle([this, icon_uri] {
FavoriteStore::Instance().RemoveFavorite(icon_uri);
return false;
});
return result;
}
result = *icon;
}
else if (desktop_icon_->RemoteUri() == icon_uri)
{
result = desktop_icon_;
}
else if (expo_icon_->RemoteUri() == icon_uri)
{
result = expo_icon_;
}
if (result)
result->Stick(emit_signal);
return result;
}
AbstractLauncherIcon::Ptr Controller::Impl::GetIconByUri(std::string const& icon_uri)
{
if (icon_uri.empty())
return AbstractLauncherIcon::Ptr();
auto const& icon = std::find_if(model_->begin(), model_->end(),
[&icon_uri](AbstractLauncherIcon::Ptr const& i) { return (i->RemoteUri() == icon_uri); });
if (icon != model_->end())
{
return *icon;
}
return AbstractLauncherIcon::Ptr();
}
SoftwareCenterLauncherIcon::Ptr Controller::Impl::CreateSCLauncherIcon(std::string const& appstream_app_id,
std::string const& aptdaemon_trans_id)
{
ApplicationPtr app = std::make_shared(appstream_app_id);
return SoftwareCenterLauncherIcon::Ptr(new SoftwareCenterLauncherIcon(app, aptdaemon_trans_id));
}
void Controller::Impl::AddRunningApps()
{
for (auto& app : ApplicationManager::Default().GetRunningApplications())
{
LOG_INFO(logger) << "Adding running app: " << app->title()
<< ", seen already: "
<< (app->seen() ? "yes" : "no");
if (!app->seen())
{
AbstractLauncherIcon::Ptr icon(CreateAppLauncherIcon(app));
icon->SkipQuirkAnimation(AbstractLauncherIcon::Quirk::VISIBLE);
RegisterIcon(icon, ++sort_priority_);
}
}
}
void Controller::Impl::AddDevices()
{
auto& fav_store = FavoriteStore::Instance();
for (auto const& icon : device_section_->GetIcons())
{
if (!icon->IsSticky() && !fav_store.IsFavorite(icon->RemoteUri()))
{
icon->SkipQuirkAnimation(AbstractLauncherIcon::Quirk::VISIBLE);
RegisterIcon(icon, ++sort_priority_);
}
}
}
void Controller::Impl::MigrateFavorites()
{
// This migrates favorites to new format, ensuring that upgrades won't lose anything
auto& favorites = FavoriteStore::Instance();
auto const& favs = favorites.GetFavorites();
auto fav_it = std::find_if(begin(favs), end(favs), [](std::string const& fav) {
return (fav.find(FavoriteStore::URI_PREFIX_UNITY) != std::string::npos);
});
if (fav_it == end(favs))
{
favorites.AddFavorite(local::RUNNING_APPS_URI, -1);
favorites.AddFavorite(expo_icon_->RemoteUri(), -1);
favorites.AddFavorite(local::DEVICES_URI, -1);
}
}
void Controller::Impl::SetupIcons()
{
MigrateFavorites();
auto& favorite_store = FavoriteStore::Instance();
FavoriteList const& favs = favorite_store.GetFavorites();
bool running_apps_added = false;
bool devices_added = false;
for (auto const& fav_uri : favs)
{
if (fav_uri == local::RUNNING_APPS_URI)
{
LOG_INFO(logger) << "Adding running apps";
AddRunningApps();
running_apps_added = true;
continue;
}
else if (fav_uri == local::DEVICES_URI)
{
LOG_INFO(logger) << "Adding devices";
AddDevices();
devices_added = true;
continue;
}
LOG_INFO(logger) << "Adding favourite: " << fav_uri;
auto const& icon = CreateFavoriteIcon(fav_uri);
if (icon)
{
icon->SkipQuirkAnimation(AbstractLauncherIcon::Quirk::VISIBLE);
RegisterIcon(icon, ++sort_priority_);
}
}
if (!running_apps_added)
{
LOG_INFO(logger) << "Adding running apps";
AddRunningApps();
}
if (!devices_added)
{
LOG_INFO(logger) << "Adding devices";
AddDevices();
}
ApplicationManager::Default().application_started
.connect(sigc::mem_fun(this, &Impl::OnApplicationStarted));
device_section_->icon_added.connect(sigc::mem_fun(this, &Impl::OnDeviceIconAdded));
favorite_store.favorite_added.connect(sigc::mem_fun(this, &Impl::OnFavoriteStoreFavoriteAdded));
favorite_store.favorite_removed.connect(sigc::mem_fun(this, &Impl::OnFavoriteStoreFavoriteRemoved));
favorite_store.reordered.connect(sigc::mem_fun(this, &Impl::ResetIconPriorities));
model_->icon_added.connect(sigc::mem_fun(&parent_->icon_added, &decltype(parent_->icon_added)::emit));
model_->icon_removed.connect(sigc::mem_fun(&parent_->icon_removed, &decltype(parent_->icon_removed)::emit));
model_->order_changed.connect(sigc::mem_fun(this, &Impl::SortAndUpdate));
model_->icon_removed.connect(sigc::mem_fun(this, &Impl::OnIconRemoved));
model_->saved.connect(sigc::mem_fun(this, &Impl::SaveIconsOrder));
}
void Controller::Impl::SendHomeActivationRequest()
{
ubus.SendMessage(UBUS_PLACE_ENTRY_ACTIVATE_REQUEST,
g_variant_new("(sus)", "home.scope", dash::NOT_HANDLED, ""));
}
Controller::Controller(XdndManager::Ptr const& xdnd_manager, ui::EdgeBarrierController::Ptr const& edge_barriers)
: options(std::make_shared())
, multiple_launchers(true)
, pimpl(new Impl(this, xdnd_manager, edge_barriers))
{}
Controller::~Controller()
{}
void Controller::UpdateNumWorkspaces(int workspaces)
{
pimpl->UpdateNumWorkspaces(workspaces);
}
Launcher& Controller::launcher() const
{
return *(pimpl->launcher_);
}
Controller::LauncherList& Controller::launchers() const
{
return pimpl->launchers;
}
std::vector Controller::GetAllShortcuts() const
{
std::vector shortcuts;
for (auto icon : *(pimpl->model_))
{
// TODO: find out why the icons use guint64 for shortcuts.
char shortcut = icon->GetShortcut();
if (shortcut)
shortcuts.push_back(shortcut);
}
return shortcuts;
}
std::vector Controller::GetAltTabIcons(bool current, bool show_desktop_disabled) const
{
std::vector results;
if (!show_desktop_disabled)
results.push_back(pimpl->desktop_icon_);
for (auto icon : *(pimpl->model_))
{
//otherwise we get two desktop icons in the switcher.
if (icon->GetIconType() != AbstractLauncherIcon::IconType::DESKTOP)
{
results.push_back(icon);
}
}
return results;
}
Window Controller::LauncherWindowId(int launcher) const
{
if (launcher >= (int)pimpl->launchers.size())
return 0;
return pimpl->launchers[launcher]->GetParent()->GetInputWindowId();
}
Window Controller::KeyNavLauncherInputWindowId() const
{
if (KeyNavIsActive())
return pimpl->keyboard_launcher_->GetParent()->GetInputWindowId();
return 0;
}
void Controller::PushToFront()
{
pimpl->launcher_->GetParent()->PushToFront();
}
int Controller::Impl::MonitorWithMouse()
{
UScreen* uscreen = UScreen::GetDefault();
return uscreen->GetMonitorWithMouse();
}
nux::ObjectPtr Controller::Impl::CurrentLauncher()
{
nux::ObjectPtr result;
int best = std::min (launchers.size() - 1, MonitorWithMouse());
if (best >= 0)
result = launchers[best];
return result;
}
void Controller::HandleLauncherKeyPress(int when)
{
pimpl->launcher_key_press_time_ = when;
auto show_launcher = [this]
{
if (pimpl->keyboard_launcher_.IsNull())
pimpl->keyboard_launcher_ = pimpl->CurrentLauncher();
pimpl->sources_.Remove(local::HIDE_TIMEOUT);
pimpl->keyboard_launcher_->ForceReveal(true);
pimpl->launcher_open = true;
return false;
};
pimpl->sources_.AddTimeout(options()->super_tap_duration, show_launcher, local::KEYPRESS_TIMEOUT);
auto show_shortcuts = [this]
{
if (!pimpl->launcher_keynav)
{
if (pimpl->keyboard_launcher_.IsNull())
pimpl->keyboard_launcher_ = pimpl->CurrentLauncher();
pimpl->keyboard_launcher_->ShowShortcuts(true);
pimpl->launcher_open = true;
}
return false;
};
pimpl->sources_.AddTimeout(local::shortcuts_show_delay, show_shortcuts, local::LABELS_TIMEOUT);
}
bool Controller::AboutToShowDash(int was_tap, int when) const
{
if ((when - pimpl->launcher_key_press_time_) < options()->super_tap_duration && was_tap)
return true;
return false;
}
void Controller::HandleLauncherKeyRelease(bool was_tap, int when)
{
int tap_duration = when - pimpl->launcher_key_press_time_;
pimpl->sources_.Remove(local::LABELS_TIMEOUT);
pimpl->sources_.Remove(local::KEYPRESS_TIMEOUT);
if (pimpl->keyboard_launcher_.IsValid())
{
pimpl->keyboard_launcher_->ShowShortcuts(false);
int ms_since_show = tap_duration;
if (ms_since_show > local::launcher_minimum_show_duration)
{
pimpl->keyboard_launcher_->ForceReveal(false);
pimpl->launcher_open = false;
if (!pimpl->launcher_keynav)
pimpl->keyboard_launcher_.Release();
}
else
{
int time_left = local::launcher_minimum_show_duration - ms_since_show;
auto hide_launcher = [this]
{
if (pimpl->keyboard_launcher_.IsValid())
{
pimpl->keyboard_launcher_->ForceReveal(false);
pimpl->launcher_open = false;
if (!pimpl->launcher_keynav)
pimpl->keyboard_launcher_.Release();
}
return false;
};
pimpl->sources_.AddTimeout(time_left, hide_launcher, local::HIDE_TIMEOUT);
}
}
}
bool Controller::HandleLauncherKeyEvent(unsigned long key_state, unsigned int key_sym, Time timestamp)
{
Display* display = nux::GetGraphicsDisplay()->GetX11Display();
// Turn the key_sym back to a keycode, this turns keypad key_sym to the correct top row key_code
unsigned int key_code = XKeysymToKeycode(display, key_sym);
// Shortcut to start launcher icons. Only relies on Keycode, ignore modifier
for (auto const& icon : *pimpl->model_)
{
unsigned int shortcut_code = XKeysymToKeycode(display, icon->GetShortcut());
if (shortcut_code == key_code)
{
if ((key_state & nux::KEY_MODIFIER_SHIFT) &&
icon->GetIconType() == AbstractLauncherIcon::IconType::APPLICATION)
{
icon->OpenInstance(ActionArg(ActionArg::Source::LAUNCHER_KEYBINDING, 0, timestamp));
}
else
{
icon->Activate(ActionArg(ActionArg::Source::LAUNCHER_KEYBINDING, 0, timestamp));
}
// disable the "tap on super" check
pimpl->launcher_key_press_time_ = 0;
return true;
}
}
return false;
}
void Controller::Impl::ReceiveMouseDownOutsideArea(int x, int y, unsigned long button_flags, unsigned long key_flags)
{
if (launcher_grabbed)
parent_->KeyNavTerminate(false);
}
void Controller::KeyNavGrab()
{
pimpl->launcher_grabbed = true;
KeyNavActivate();
pimpl->keyboard_launcher_->GrabKeyboard();
pimpl->launcher_key_press_connection_ =
pimpl->keyboard_launcher_->key_down.connect(sigc::mem_fun(pimpl.get(), &Controller::Impl::ReceiveLauncherKeyPress));
pimpl->launcher_event_outside_connection_ =
pimpl->keyboard_launcher_->mouse_down_outside_pointer_grab_area.connect(sigc::mem_fun(pimpl.get(), &Controller::Impl::ReceiveMouseDownOutsideArea));
pimpl->launcher_key_nav_terminate_ =
pimpl->keyboard_launcher_->key_nav_terminate_request.connect([this] {
KeyNavTerminate(false);
});
}
void Controller::KeyNavActivate()
{
if (pimpl->launcher_keynav)
return;
pimpl->launcher_keynav = true;
pimpl->keynav_restore_window_ = true;
pimpl->keyboard_launcher_ = pimpl->CurrentLauncher();
pimpl->keyboard_launcher_->EnterKeyNavMode();
pimpl->model_->SetSelection(0);
if (pimpl->launcher_grabbed)
{
pimpl->ubus.SendMessage(UBUS_LAUNCHER_START_KEY_NAV,
glib::Variant(pimpl->keyboard_launcher_->monitor()));
}
else
{
pimpl->ubus.SendMessage(UBUS_LAUNCHER_START_KEY_SWITCHER,
glib::Variant(pimpl->keyboard_launcher_->monitor()));
}
AbstractLauncherIcon::Ptr const& selected = pimpl->model_->Selection();
if (selected)
{
if (selected->GetIconType() == AbstractLauncherIcon::IconType::HOME)
{
pimpl->ubus.SendMessage(UBUS_DASH_ABOUT_TO_SHOW, NULL);
}
pimpl->ubus.SendMessage(UBUS_LAUNCHER_SELECTION_CHANGED,
glib::Variant(selected->tooltip_text()));
}
}
void Controller::KeyNavNext()
{
pimpl->model_->SelectNext();
AbstractLauncherIcon::Ptr const& selected = pimpl->model_->Selection();
if (selected)
{
if (selected->GetIconType() == AbstractLauncherIcon::IconType::HOME)
{
pimpl->ubus.SendMessage(UBUS_DASH_ABOUT_TO_SHOW, NULL);
}
pimpl->ubus.SendMessage(UBUS_LAUNCHER_SELECTION_CHANGED,
glib::Variant(selected->tooltip_text()));
}
}
void Controller::KeyNavPrevious()
{
pimpl->model_->SelectPrevious();
AbstractLauncherIcon::Ptr const& selected = pimpl->model_->Selection();
if (selected)
{
if (selected->GetIconType() == AbstractLauncherIcon::IconType::HOME)
{
pimpl->ubus.SendMessage(UBUS_DASH_ABOUT_TO_SHOW, NULL);
}
pimpl->ubus.SendMessage(UBUS_LAUNCHER_SELECTION_CHANGED,
glib::Variant(selected->tooltip_text()));
}
}
void Controller::KeyNavTerminate(bool activate)
{
if (!pimpl->launcher_keynav)
return;
pimpl->keyboard_launcher_->ExitKeyNavMode();
if (pimpl->launcher_grabbed)
{
pimpl->keyboard_launcher_->UnGrabKeyboard();
pimpl->launcher_key_press_connection_->disconnect();
pimpl->launcher_event_outside_connection_->disconnect();
pimpl->launcher_key_nav_terminate_->disconnect();
pimpl->launcher_grabbed = false;
pimpl->ubus.SendMessage(UBUS_LAUNCHER_END_KEY_NAV,
glib::Variant(pimpl->keynav_restore_window_));
}
else
{
pimpl->ubus.SendMessage(UBUS_LAUNCHER_END_KEY_SWITCHER,
glib::Variant(pimpl->keynav_restore_window_));
}
if (activate)
{
auto timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp;
pimpl->sources_.AddIdle([this, timestamp] {
pimpl->model_->Selection()->Activate(ActionArg(ActionArg::Source::LAUNCHER, 0, timestamp));
return false;
});
}
pimpl->launcher_keynav = false;
if (!pimpl->launcher_open)
pimpl->keyboard_launcher_.Release();
}
bool Controller::KeyNavIsActive() const
{
return pimpl->launcher_keynav;
}
bool Controller::IsOverlayOpen() const
{
for (auto launcher_ptr : pimpl->launchers)
{
if (launcher_ptr->IsOverlayOpen())
return true;
}
return false;
}
void Controller::ClearTooltips()
{
for (auto launcher_ptr : pimpl->launchers)
launcher_ptr->ClearTooltip();
}
std::string
Controller::GetName() const
{
return "LauncherController";
}
void
Controller::AddProperties(debug::IntrospectionData& introspection)
{
timespec current;
clock_gettime(CLOCK_MONOTONIC, ¤t);
introspection
.add("key_nav_is_active", KeyNavIsActive())
.add("key_nav_launcher_monitor", pimpl->keyboard_launcher_.IsValid() ? pimpl->keyboard_launcher_->monitor : -1)
.add("key_nav_selection", pimpl->model_->SelectionIndex())
.add("key_nav_is_grabbed", pimpl->launcher_grabbed)
.add("keyboard_launcher", pimpl->CurrentLauncher()->monitor);
}
void Controller::Impl::ReceiveLauncherKeyPress(unsigned long eventType,
unsigned long keysym,
unsigned long state,
const char* character,
unsigned short keyCount)
{
/*
* all key events below are related to keynavigation. Make an additional
* check that we are in a keynav mode when we inadvertadly receive the focus
*/
if (!launcher_grabbed)
return;
switch (keysym)
{
// up
case NUX_VK_UP:
case NUX_KP_UP:
if (Settings::Instance().launcher_position() == LauncherPosition::LEFT)
// move selection up or go to global-menu if at top-most icon
parent_->KeyNavPrevious();
else
OpenQuicklist();
break;
// down
case NUX_VK_DOWN:
case NUX_KP_DOWN:
if (Settings::Instance().launcher_position() == LauncherPosition::LEFT)
// move selection down and unfold launcher if needed
parent_->KeyNavNext();
else
// exit launcher key-focus
parent_->KeyNavTerminate(false);
break;
// left
case NUX_VK_LEFT:
case NUX_KP_LEFT:
if (Settings::Instance().launcher_position() == LauncherPosition::LEFT)
parent_->KeyNavTerminate(false);
else
// move selection left or go to global-menu if at top-most icon or close quicklist
parent_->KeyNavPrevious();
break;
// right
case NUX_VK_RIGHT:
case NUX_KP_RIGHT:
if (Settings::Instance().launcher_position() == LauncherPosition::LEFT)
OpenQuicklist();
else
// move selection right and unfold launcher if needed
parent_->KeyNavNext();
break;
// super/control/esc (close quicklist or exit laucher key-focus)
case NUX_VK_LWIN:
case NUX_VK_RWIN:
case NUX_VK_CONTROL:
case NUX_VK_ESCAPE:
// hide again
parent_->KeyNavTerminate(false);
break;
// shift-f10 (open quicklist of currently selected icon)
case XK_F10:
if (!(state & nux::NUX_STATE_SHIFT))
break;
case XK_Menu:
OpenQuicklist();
break;
// (open a new instance)
case NUX_VK_SPACE:
{
auto timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp;
model_->Selection()->OpenInstance(ActionArg(ActionArg::Source::LAUNCHER, 0, timestamp));
parent_->KeyNavTerminate(false);
break;
}
// (start/activate currently selected icon)
case NUX_VK_ENTER:
case NUX_KP_ENTER:
parent_->KeyNavTerminate(true);
break;
default:
// ALT + ; treat it as a shortcut and exit keynav
if (state & nux::NUX_STATE_ALT)
{
parent_->KeyNavTerminate(false);
}
break;
}
}
void Controller::Impl::OpenQuicklist()
{
if (model_->Selection()->OpenQuicklist(true, keyboard_launcher_->monitor(), keynav_restore_window_))
{
keynav_restore_window_ = false;
parent_->KeyNavTerminate(false);
}
}
GVariant* Controller::Impl::OnDBusMethodCall(std::string const& method, GVariant *parameters)
{
if (method == "AddLauncherItem")
{
glib::String appstream_app_id, aptdaemon_trans_id;
g_variant_get(parameters, "(ss)", &appstream_app_id, &aptdaemon_trans_id);
OnLauncherAddRequestSpecial(appstream_app_id.Str(), aptdaemon_trans_id.Str());
}
else if (method == "UpdateLauncherIconFavoriteState")
{
gboolean is_sticky;
glib::String icon_uri;
g_variant_get(parameters, "(sb)", &icon_uri, &is_sticky);
OnLauncherUpdateIconStickyState(icon_uri.Str(), is_sticky);
}
return nullptr;
}
} // namespace launcher
} // namespace unity