// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* * Copyright (C) 2011-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: Mikkel Kamstrup Erlandsen * Marco Trevisan (TreviƱo) <3v1n0@ubuntu.com> */ #include #include #include "LauncherEntryRemoteModel.h" namespace unity { DECLARE_LOGGER(logger, "unity.launcher.entry.remote.model"); /** * Helper class implementing the remote API to control the icons in the * launcher. Also known as the com.canonical.Unity.LauncherEntry DBus API. * It enables clients to dynamically control their launcher icons by * adding a counter, progress indicator, or emblem to it. It also supports * adding a quicklist menu by means of the dbusmenu library. * * We take care to print out any client side errors or oddities in detail, * in order to help third party developers as much as possible when integrating * with Unity. */ LauncherEntryRemoteModel::LauncherEntryRemoteModel() : _launcher_entry_dbus_signal_id(0) , _dbus_name_owner_changed_signal_id(0) { glib::Error error; _conn = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (error) { LOG_ERROR(logger) << "Unable to connect to session bus: " << error.Message(); return; } /* Listen for *all* signals on the "com.canonical.Unity.LauncherEntry" * interface, no matter who the sender is */ _launcher_entry_dbus_signal_id = g_dbus_connection_signal_subscribe(_conn, nullptr, // sender "com.canonical.Unity.LauncherEntry", // iface nullptr, // member nullptr, // path nullptr, // arg0 G_DBUS_SIGNAL_FLAGS_NONE, &OnEntrySignalReceived, this, nullptr); _dbus_name_owner_changed_signal_id = g_dbus_connection_signal_subscribe(_conn, "org.freedesktop.DBus", // sender "org.freedesktop.DBus", // interface "NameOwnerChanged", // member "/org/freedesktop/DBus", // path nullptr, // arg0 G_DBUS_SIGNAL_FLAGS_NONE, &OnDBusNameOwnerChanged, this, nullptr); } LauncherEntryRemoteModel::~LauncherEntryRemoteModel() { if (_conn) { if (_launcher_entry_dbus_signal_id) { g_dbus_connection_signal_unsubscribe(_conn, _launcher_entry_dbus_signal_id); } if (_dbus_name_owner_changed_signal_id) { g_dbus_connection_signal_unsubscribe(_conn, _dbus_name_owner_changed_signal_id); } } } /** * Return the number of unique LauncherEntryRemote objects managed by the model. * The entries are identified by their LauncherEntryRemote::AppUri property. */ unsigned int LauncherEntryRemoteModel::Size() const { return _entries_by_uri.size(); } /** * Return a smart pointer to a LauncherEntryRemote if there is one for app_uri, * otherwise nullptr. * * App Uris look like application://$desktop_file_id, where desktop_file_id * is the base name of the .desktop file for the application including the * .desktop extension. Eg. application://firefox.desktop. */ LauncherEntryRemote::Ptr LauncherEntryRemoteModel::LookupByUri(std::string const& app_uri) { auto target_en = _entries_by_uri.find(app_uri); return (target_en != _entries_by_uri.end()) ? target_en->second : nullptr; } /** * Return a smart pointer to a LauncherEntryRemote if there is one for desktop_id, * otherwise nullptr. * * The desktop id is the base name of the .desktop file for the application * including the .desktop extension. Eg. firefox.desktop. */ LauncherEntryRemote::Ptr LauncherEntryRemoteModel::LookupByDesktopId(std::string const& desktop_id) { std::string prefix = "application://"; return LookupByUri(prefix + desktop_id); } /** * Return a smart pointer to a LauncherEntryRemote if there is one for * desktop_file_path, otherwise nullptr. */ LauncherEntryRemote::Ptr LauncherEntryRemoteModel::LookupByDesktopFile(std::string const& desktop_file_path) { std::string const& desktop_id = DesktopUtilities::GetDesktopID(desktop_file_path); if (desktop_id.empty()) return nullptr; return LookupByDesktopId(desktop_id); } /** * Get a list of all application URIs which have registered with the launcher * API. */ std::list LauncherEntryRemoteModel::GetUris() const { std::list uris; for (auto entry : _entries_by_uri) uris.push_back(entry.first); return uris; } /** * Add or update a remote launcher entry. */ void LauncherEntryRemoteModel::AddEntry(LauncherEntryRemote::Ptr const& entry) { auto existing_entry = LookupByUri(entry->AppUri()); if (existing_entry) { existing_entry->Update(entry); } else { _entries_by_uri[entry->AppUri()] = entry; entry_added.emit(entry); } } /** * Add or update a remote launcher entry. */ void LauncherEntryRemoteModel::RemoveEntry(LauncherEntryRemote::Ptr const& entry) { _entries_by_uri.erase(entry->AppUri()); entry_removed.emit(entry); } /** * Handle an incoming Update() signal from DBus */ void LauncherEntryRemoteModel::HandleUpdateRequest(std::string const& sender_name, GVariant* parameters) { if (!parameters) return; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv})"))) { LOG_ERROR(logger) << "Received 'com.canonical.Unity.LauncherEntry.Update' with" " illegal payload signature '" << g_variant_get_type_string(parameters) << "'. Expected '(sa{sv})'."; return; } glib::String app_uri; GVariantIter* prop_iter; g_variant_get(parameters, "(sa{sv})", &app_uri, &prop_iter); auto entry = LookupByUri(app_uri.Str()); if (entry) { /* It's important that we update the DBus name first since it might * unset the quicklist if it changes */ entry->SetDBusName(sender_name); entry->Update(prop_iter); } else { LauncherEntryRemote::Ptr entry_ptr(new LauncherEntryRemote(sender_name, parameters)); AddEntry(entry_ptr); } g_variant_iter_free(prop_iter); } void LauncherEntryRemoteModel::OnEntrySignalReceived(GDBusConnection* connection, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { auto self = static_cast(user_data); if (!parameters || !signal_name) { LOG_ERROR(logger) << "Received DBus signal '" << interface_name << "." << signal_name << "' with empty payload from " << sender_name; return; } if (std::string(signal_name) == "Update") { if (!sender_name) { LOG_ERROR(logger) << "Received 'com.canonical.Unity.LauncherEntry.Update' from" " an undefined sender. This may happen if you are trying " "to run Unity on a p2p DBus connection."; return; } self->HandleUpdateRequest(sender_name, parameters); } else { LOG_ERROR(logger) << "Unknown signal '" << interface_name << "." << signal_name << "' from " << sender_name; } } void LauncherEntryRemoteModel::OnDBusNameOwnerChanged(GDBusConnection* connection, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { auto self = static_cast(user_data); if (!parameters || self->_entries_by_uri.empty()) return; glib::String name, before, after; g_variant_get(parameters, "(sss)", &name, &before, &after); if (!after || after.Str().empty()) { // Name gone, find and destroy LauncherEntryRemote std::vector to_rm; for (auto it = self->_entries_by_uri.begin(); it != self->_entries_by_uri.end(); ++it) { auto entry = it->second; if (entry->DBusName() == name.Str()) { to_rm.push_back(entry); } } for (auto entry : to_rm) { self->RemoveEntry(entry); } } } }