// -*- 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