From 593b93c8452ba809f6520d7f5a2eea15c92ad1ab Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 19 Jul 2019 21:46:44 +0200 Subject: [PATCH 1/3] Add media browser support to kodi --- kodi/devicepluginkodi.cpp | 49 +++- kodi/devicepluginkodi.h | 5 + kodi/devicepluginkodi.json | 12 +- kodi/kodi.cpp | 535 ++++++++++++++++++++++++++++++++++++- kodi/kodi.h | 37 ++- kodi/kodiconnection.cpp | 2 +- kodi/kodiconnection.h | 3 +- kodi/kodijsonhandler.cpp | 17 +- kodi/kodijsonhandler.h | 1 + 9 files changed, 650 insertions(+), 11 deletions(-) diff --git a/kodi/devicepluginkodi.cpp b/kodi/devicepluginkodi.cpp index 68642b31..30eadd35 100644 --- a/kodi/devicepluginkodi.cpp +++ b/kodi/devicepluginkodi.cpp @@ -63,7 +63,10 @@ void DevicePluginKodi::init() Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) { qCDebug(dcKodi) << "Setup Kodi device" << device->paramValue(kodiDeviceIpParamTypeId).toString(); - Kodi *kodi= new Kodi(QHostAddress(device->paramValue(kodiDeviceIpParamTypeId).toString()), 9090, this); + QString ipString = device->paramValue(kodiDeviceIpParamTypeId).toString(); + int port = device->paramValue(kodiDevicePortParamTypeId).toInt(); + int httpPort = device->paramValue(kodiDeviceHttpPortParamTypeId).toInt(); + Kodi *kodi= new Kodi(QHostAddress(ipString), port, httpPort, this); connect(kodi, &Kodi::connectionStatusChanged, this, &DevicePluginKodi::onConnectionChanged); connect(kodi, &Kodi::stateChanged, this, &DevicePluginKodi::onStateChanged); @@ -71,6 +74,8 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) connect(kodi, &Kodi::versionDataReceived, this, &DevicePluginKodi::versionDataReceived); connect(kodi, &Kodi::updateDataReceived, this, &DevicePluginKodi::onSetupFinished); connect(kodi, &Kodi::playbackStatusChanged, this, &DevicePluginKodi::onPlaybackStatusChanged); + connect(kodi, &Kodi::browseResult, this, &DevicePluginKodi::browseRequestFinished); + connect(kodi, &Kodi::browserItemResult, this, &DevicePluginKodi::browserItemRequestFinished); connect(kodi, &Kodi::activePlayerChanged, device, [device](const QString &playerType){ device->setStateValue(kodiPlayerTypeStateTypeId, playerType); @@ -105,7 +110,10 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) connect(reply, &QNetworkReply::finished, device, [device, reply, addr](){ reply->deleteLater(); QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); - device->setStateValue(kodiArtworkStateTypeId, "http://" + addr + ":8080/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString()); + QString fileUrl = "http://" + addr + ":8080/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString(); + qCDebug(dcKodi()) << "DL result:" << jsonDoc.toJson(); + qCDebug(dcKodi()) << "Resolved url:" << fileUrl; + device->setStateValue(kodiArtworkStateTypeId, fileUrl); }); }); @@ -222,6 +230,43 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action return Device::DeviceErrorDeviceClassNotFound; } +Device::BrowseResult DevicePluginKodi::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) +{ + Q_UNUSED(locale) + + Kodi *kodi = m_kodis.key(device); + if (!kodi) { + result.status = Device::DeviceErrorHardwareNotAvailable; + return result; + } + + return kodi->browse(itemId, result); +} + +Device::BrowserItemResult DevicePluginKodi::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) +{ + Q_UNUSED(locale) + + Kodi *kodi = m_kodis.key(device); + if (!kodi) { + result.status = Device::DeviceErrorHardwareNotAvailable; + return result; + } + + return kodi->browserItem(itemId, result); + +} + +Device::DeviceError DevicePluginKodi::executeBrowserItem(Device *device, const BrowserAction &browserAction) +{ + Kodi *kodi = m_kodis.key(device); + if (!kodi) { + return Device::DeviceErrorHardwareNotAvailable; + } + + return kodi->launchBrowserItem(browserAction.itemId()); +} + void DevicePluginKodi::onPluginTimer() { foreach (Kodi *kodi, m_kodis.keys()) { diff --git a/kodi/devicepluginkodi.h b/kodi/devicepluginkodi.h index ba35b247..3b6ccc64 100644 --- a/kodi/devicepluginkodi.h +++ b/kodi/devicepluginkodi.h @@ -47,6 +47,11 @@ public: Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; Device::DeviceError executeAction(Device *device, const Action &action) override; + Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override; + Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) override; + Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction) override; +// Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override; + private: PluginTimer *m_pluginTimer; QHash m_kodis; diff --git a/kodi/devicepluginkodi.json b/kodi/devicepluginkodi.json index cc057403..34412b63 100644 --- a/kodi/devicepluginkodi.json +++ b/kodi/devicepluginkodi.json @@ -14,19 +14,27 @@ "displayName": "Kodi", "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"], "createMethods": ["user", "discovery"], + "browsable": true, "paramTypes": [ { "id": "1a897065-57c6-49b3-bac9-1e5db27859e5", "name": "ip", - "displayName": "ip", + "displayName": "IP Address", "type" : "QString", "inputType": "IPv4Address" }, { "id": "660fb4d7-9479-4c9d-a900-ce221d2b8ae4", "name": "port", - "displayName": "port", + "displayName": "Port", "type" : "int" + }, + { + "id": "27ea7e46-80f0-49ea-9352-b57c78905c67", + "name": "httpPort", + "displayName": "HTTP port", + "type" : "int", + "defaultValue": 8080 } ], "stateTypes": [ diff --git a/kodi/kodi.cpp b/kodi/kodi.cpp index e38ed177..13ac75fe 100644 --- a/kodi/kodi.cpp +++ b/kodi/kodi.cpp @@ -25,8 +25,9 @@ #include "extern-plugininfo.h" #include -Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) : +Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *parent) : QObject(parent), + m_httpPort(httpPort), m_muted(false), m_volume(-1) { @@ -36,6 +37,113 @@ Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) : m_jsonHandler = new KodiJsonHandler(m_connection, this); connect(m_jsonHandler, &KodiJsonHandler::notificationReceived, this, &Kodi::processNotification); connect(m_jsonHandler, &KodiJsonHandler::replyReceived, this, &Kodi::processResponse); + + + // Init FS + m_virtualFs = new VirtualFsNode(BrowserItem()); + + QVariantMap sort; + sort.insert("method", "label"); + sort.insert("ignorearticle", true); + + QVariantList properties; + properties.append("thumbnail"); + + // Music + BrowserItem item = BrowserItem("audiolibrary", tr("Music library"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *audioLibrary = new VirtualFsNode(item); + m_virtualFs->addChild(audioLibrary); + + item = BrowserItem("artists", tr("Artists"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *artists = new VirtualFsNode(item); + artists->getMethod = "AudioLibrary.GetArtists"; + artists->getParams.insert("sort", sort); + artists->getParams.insert("properties", properties); + audioLibrary->addChild(artists); + + item = BrowserItem("albums", tr("Albums"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *albums = new VirtualFsNode(item); + albums->getMethod = "AudioLibrary.GetAlbums"; + albums->getParams.insert("sort", sort); + albums->getParams.insert("properties", properties); + audioLibrary->addChild(albums); + + item = BrowserItem("songs", tr("Songs"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *songs = new VirtualFsNode(item); + songs->getMethod = "AudioLibrary.GetSongs"; + songs->getParams.insert("sort", sort); + songs->getParams.insert("properties", properties); + audioLibrary->addChild(songs); + + // Video + item = BrowserItem("videolibrary", tr("Video library"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *videoLibrary = new VirtualFsNode(item); + m_virtualFs->addChild(videoLibrary); + + item = BrowserItem("movies", tr("Movies"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *movies = new VirtualFsNode(item); + movies->getMethod = "VideoLibrary.GetMovies"; + movies->getParams.insert("sort", sort); + movies->getParams.insert("properties", properties); + videoLibrary->addChild(movies); + + item = BrowserItem("tvshows", tr("TV Shows"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *tvShows = new VirtualFsNode(item); + tvShows->getMethod = "VideoLibrary.GetTVShows"; + tvShows->getParams.insert("sort", sort); + tvShows->getParams.insert("properties", properties); + videoLibrary->addChild(tvShows); + + item = BrowserItem("musicvideos", tr("Music Videos"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *musicVideos = new VirtualFsNode(item); + musicVideos->getMethod = "VideoLibrary.GetMusicVideos"; + musicVideos->getParams.insert("sort", sort); + musicVideos->getParams.insert("properties", properties); + videoLibrary->addChild(musicVideos); + + item = BrowserItem("addons", tr("Add-ons"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *addons = new VirtualFsNode(item); + m_virtualFs->addChild(addons); + + item = BrowserItem("videoaddons", tr("Video add-ons"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *videoAddons = new VirtualFsNode(item); + videoAddons->getMethod = "Files.GetDirectory"; + videoAddons->getParams.insert("directory", "addons://sources/video"); + videoAddons->getParams.insert("sort", sort); + videoAddons->getParams.insert("properties", properties); + addons->addChild(videoAddons); + + item = BrowserItem("musicaddons", tr("Music add-ons"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *musicAddons = new VirtualFsNode(item); + musicAddons->getMethod = "Files.GetDirectory"; + musicAddons->getParams.insert("directory", "addons://sources/audio"); + musicAddons->getParams.insert("sort", sort); + musicAddons->getParams.insert("properties", properties); + addons->addChild(musicAddons); + + } QHostAddress Kodi::hostAddress() const @@ -190,6 +298,213 @@ void Kodi::disconnectKodi() m_connection->disconnectKodi(); } +Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &result) +{ +// m_jsonHandler->sendData() + VirtualFsNode *node = m_virtualFs->findNode(itemId); + + if (node) { + if (node->getMethod.isEmpty()) { + foreach (VirtualFsNode *child, node->childs) { + result.items.append(child->item); + } + return result; + } + + qCDebug(dcKodi()) << "Sending:" << node->getMethod << node->getParams; + int id = m_jsonHandler->sendData(node->getMethod, node->getParams); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + QVariantMap sort; + sort.insert("method", "label"); + sort.insert("ignorearticle", true); + QVariantList properties; + properties.append("thumbnail"); + + if (itemId.startsWith("artist:")) { + QString idString = itemId; + idString.remove(QRegExp("^artist:")); + QVariantMap filter; + filter.insert("artistid", idString.toInt()); + QVariantMap params; + params.insert("filter", filter); + params.insert("properties", properties); + int id = m_jsonHandler->sendData("AudioLibrary.GetAlbums", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + if (itemId.startsWith("album:")) { + QString idString = itemId; + idString.remove(QRegExp("^album:")); + QVariantMap filter; + filter.insert("albumid", idString.toInt()); + QVariantMap params; + params.insert("filter", filter); + QVariantList properties; + properties.append("thumbnail"); + properties.append("albumid"); + params.insert("properties", properties); + int id = m_jsonHandler->sendData("AudioLibrary.GetSongs", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + if (itemId.startsWith("tvshow:")) { + QString idString = itemId; + idString.remove(QRegExp("^tvshow:")); + QVariantMap params; + params.insert("tvshowid", idString.toInt()); + QVariantList properties; + properties.append("tvshowid"); + properties.append("season"); + properties.append("thumbnail"); + params.insert("properties", properties); + int id = m_jsonHandler->sendData("VideoLibrary.GetSeasons", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + if (itemId.startsWith("season:")) { + QString idString = itemId; + idString.remove(QRegExp("^season:")); + int seasonId = idString.left(idString.indexOf(",")).toInt(); + idString.remove(QRegExp("^[0-9]*,tvshow:")); + int tvShowId = idString.toInt(); + QVariantMap params; + params.insert("tvshowid", tvShowId); + params.insert("season", seasonId); + params.insert("properties", properties); + qCDebug(dcKodi()) << "getting episodes:" << params; + int id = m_jsonHandler->sendData("VideoLibrary.GetEpisodes", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + if (itemId.startsWith("addon:")) { + QString idString = itemId; + idString.remove(QRegExp("^addon:")); + QVariantMap params; + params.insert("directory", "plugin://" + idString); +// QVariantList properties; +// properties.append("tvshowid"); +// properties.append("season"); +// params.insert("properties", properties); + qCDebug(dcKodi()) << "Sending" << params; + int id = m_jsonHandler->sendData("Files.GetDirectory", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + if (itemId.startsWith("file:")) { + QString idString = itemId; + idString.remove(QRegExp("^file:")); + QVariantMap params; + params.insert("directory", idString); + params.insert("properties", properties); + qCDebug(dcKodi()) << "Sending" << params; + int id = m_jsonHandler->sendData("Files.GetDirectory", params); + m_pendingBrowseRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; + } + + result.status = Device::DeviceErrorItemNotFound; + return result; +} + +Device::BrowserItemResult Kodi::browserItem(const QString &itemId, Device::BrowserItemResult &result) +{ + qCDebug(dcKodi()) << "Getting details for" << itemId; + QString idString = itemId; + QString method; + QVariantMap params; + if (idString.startsWith("song:")) { + idString.remove(QRegExp("^song:")); + params.insert("songid", idString.toInt()); + method = "AudioLibrary.GetSongDetails"; + } else if (idString.startsWith("movie:")) { + idString.remove(QRegExp("^movie:")); + params.insert("movieid", idString.toInt()); + method = "VideoLibrary.GetMovieDetails"; + } else if (idString.startsWith("episode:")) { + idString.remove(QRegExp("^episode:")); + params.insert("episodeid", idString.toInt()); + method = "VideoLibrary.GetEpisodeDetails"; + } else if (idString.startsWith("musicvideo:")) { + idString.remove(QRegExp("^musicvideo:")); + params.insert("musicvideoid", idString.toInt()); + method = "VideoLibrary.GetMusicVideoDetails"; + } else { + qCWarning(dcKodi()) << "Unhandled browserItem request!" << itemId; + result.status = Device::DeviceErrorUnsupportedFeature; + return result; + } + int id = m_jsonHandler->sendData(method, params); + m_pendingBrowserItemRequests.insert(id, result); + result.status = Device::DeviceErrorAsync; + return result; +} + +Device::DeviceError Kodi::launchBrowserItem(const QString &itemId) +{ + qCDebug(dcKodi()) << "Launching" << itemId; + QVariantMap playlistItem; + + QString idString = itemId; + if (idString.startsWith("song:")) { + idString.remove(QRegExp("^song:")); + int idx = idString.indexOf(",album:"); + if (idx > 0) { + int position = idString.left(idx).toInt(); + idString.remove(QRegExp("^[0-9]*,album:")); + int albumId = idString.toInt(); + + QVariantMap params; + params.insert("playlistid", 0); + m_jsonHandler->sendData("Playlist.Clear", params); + + QVariantMap item; + item.insert("albumid", albumId); + params.insert("item", item); + m_jsonHandler->sendData("Playlist.Add", params); + + playlistItem.insert("playlistid", 0); + playlistItem.insert("position", position); + + } else { + playlistItem.insert("songid", idString.toInt()); + } + } else if (idString.startsWith("movie:")) { + idString.remove(QRegExp("^movie:")); + playlistItem.insert("movieid", idString.toInt()); + } else if (idString.startsWith("episode:")) { + idString.remove(QRegExp("^episode:")); + playlistItem.insert("episodeid", idString.toInt()); + } else if (idString.startsWith("file:")) { + idString.remove(QRegExp("^file:")); + playlistItem.insert("file", idString); + } else { + qCWarning(dcKodi()) << "Unhandled launchBrowserItem request!" << itemId; + return Device::DeviceErrorItemNotFound; + } + QVariantMap params; + params.clear(); + params.insert("item", playlistItem); + + qCDebug(dcKodi()) << "Player.Open" << params; + m_jsonHandler->sendData("Player.Open", params); + return Device::DeviceErrorNoError; +} + void Kodi::onVolumeChanged(const int &volume, const bool &muted) { if (m_volume != volume || m_muted != muted) { @@ -346,11 +661,213 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res if (method == "Player.SetShuffle" || method == "Player.SetRepeat") { updatePlayerProperties(); + emit actionExecuted(id, true); + return; } - emit actionExecuted(id, true); + if (method == "AudioLibrary.GetArtists") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &artistVariant, response.value("result").toMap().value("artists").toList()) { + QVariantMap artist = artistVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << artist; + BrowserItem item("artist:" + artist.value("artistid").toString(), artist.value("label").toString()); + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setThumbnail(prepareThumbnail(artist.value("thumbnail").toString())); + qCDebug(dcKodi()) << "Thumbnail" << item.thumbnail(); + result.items.append(item); + } + emit browseResult(result); + return; + } - qCDebug(dcKodi()) << "unhandled reply" << method << response; + if (method == "AudioLibrary.GetAlbums") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &albumVariant, response.value("result").toMap().value("albums").toList()) { + QVariantMap album = albumVariant.toMap(); + BrowserItem item("album:" + album.value("albumid").toString(), album.value("label").toString()); + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setThumbnail(prepareThumbnail(album.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "AudioLibrary.GetSongs") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + int i = 0; + foreach (const QVariant &songVariant, response.value("result").toMap().value("songs").toList()) { + QVariantMap song = songVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << song; + QString newId = "song:"; + if (song.contains("albumid")) { + newId += QString::number(i); + newId += ",album:" + song.value("albumid").toString(); + } else { + newId += song.value("songid").toString(); + } + BrowserItem item(newId, song.value("label").toString()); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconMusic); + item.setThumbnail(prepareThumbnail(song.value("thumbnail").toString())); + result.items.append(item); + i++; + } + emit browseResult(result); + return; + } + + + if (method == "VideoLibrary.GetMovies") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &movieVariant, response.value("result").toMap().value("movies").toList()) { + QVariantMap movie = movieVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << movie; + BrowserItem item("movie:" + movie.value("movieid").toString(), movie.value("label").toString()); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconVideo); + item.setThumbnail(prepareThumbnail(movie.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "VideoLibrary.GetTVShows") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &tvShowVariant, response.value("result").toMap().value("tvshows").toList()) { + QVariantMap tvShow = tvShowVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << tvShow; + BrowserItem item("tvshow:" + tvShow.value("tvshowid").toString(), tvShow.value("label").toString()); + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setThumbnail(prepareThumbnail(tvShow.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "VideoLibrary.GetSeasons") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &seasonVariant, response.value("result").toMap().value("seasons").toList()) { + QVariantMap season = seasonVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << season; + BrowserItem item("season:" + season.value("season").toString() + ",tvshow:" + season.value("tvshowid").toString(), season.value("label").toString()); + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setThumbnail(prepareThumbnail(season.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "VideoLibrary.GetEpisodes") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &episodeVariant, response.value("result").toMap().value("episodes").toList()) { + QVariantMap episode = episodeVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << episode; + BrowserItem item("episode:" + episode.value("episodeid").toString(), episode.value("label").toString()); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconVideo); + item.setThumbnail(prepareThumbnail(episode.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "VideoLibrary.GetMusicVideos") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &musicVideoVariant, response.value("result").toMap().value("musicvideos").toList()) { + QVariantMap musicVideo = musicVideoVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << musicVideo; + BrowserItem item("musicvideo:" + musicVideo.value("musicvideoid").toString(), musicVideo.value("label").toString()); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconVideo); + item.setThumbnail(prepareThumbnail(musicVideo.value("thumbnail").toString())); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "Addons.GetAddons") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &addonVariant, response.value("result").toMap().value("addons").toList()) { + QVariantMap addon = addonVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << addon; + BrowserItem item("addon:" + addon.value("addonid").toString(), addon.value("name").toString()); + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconApplication); + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "Files.GetDirectory") { + Device::BrowseResult result = m_pendingBrowseRequests.take(id); + foreach (const QVariant &fileVariant, response.value("result").toMap().value("files").toList()) { + QVariantMap file = fileVariant.toMap(); + qCDebug(dcKodi()) << "Entry:" << file; + BrowserItem item("file:" + file.value("file").toString(), file.value("label").toString()); + if (file.value("type").toString() == "directory" || file.value("type").toString() == "unknown") { + item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + } else if (file.value("type").toString() == "episode" || file.value("type").toString() == "movie") { + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconVideo); + } else if (file.value("type").toString() == "song") { + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconMusic); + } + result.items.append(item); + } + emit browseResult(result); + return; + } + + if (method == "AudioLibrary.GetSongDetails") { + Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id); + result.item.setId("song:" + response.value("result").toMap().value("songdetails").toMap().value("songid").toString()); + result.item.setDisplayName(response.value("result").toMap().value("songdetails").toMap().value("label").toString()); + qCDebug(dcKodi()) << "Song details:" << result.item.displayName(); + emit browserItemResult(result); + return; + } + + if (method == "VideoLibrary.GetMovieDetails") { + Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id); + result.item.setId("movie:" + response.value("result").toMap().value("moviedetails").toMap().value("movieid").toString()); + result.item.setDisplayName(response.value("result").toMap().value("moviedetails").toMap().value("label").toString()); + qCDebug(dcKodi()) << "Movie details:" << result.item.displayName(); + emit browserItemResult(result); + return; + } + + if (method == "VideoLibrary.GetEpisodeDetails") { + Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id); + result.item.setId("movie:" + response.value("result").toMap().value("episodedetails").toMap().value("episodeid").toString()); + result.item.setDisplayName(response.value("result").toMap().value("episodedetails").toMap().value("label").toString()); + qCDebug(dcKodi()) << "Episode details:" << result.item.displayName(); + emit browserItemResult(result); + return; + } + + if (method == "VideoLibrary.GetMusicVideoDetails") { + Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id); + result.item.setId("movie:" + response.value("result").toMap().value("musicvideodetails").toMap().value("musicvideoid").toString()); + result.item.setDisplayName(response.value("result").toMap().value("musicvideodetails").toMap().value("label").toString()); + qCDebug(dcKodi()) << "Episode details:" << result.item.displayName(); + emit browserItemResult(result); + return; + } + + qCWarning(dcKodi()) << "unhandled reply" << method << response; } void Kodi::updatePlayerProperties() @@ -372,3 +889,15 @@ void Kodi::updateMetadata() params.insert("properties", fields); m_jsonHandler->sendData("Player.GetItem", params); } + +QString Kodi::prepareThumbnail(const QString &thumbnail) +{ + if (thumbnail.isEmpty()) { + return QString(); + } + + return QString("http://%1:%2/image/%3") + .arg(m_connection->hostAddress().toString()) + .arg(m_httpPort) + .arg(QString(thumbnail.toUtf8().toPercentEncoding())); +} diff --git a/kodi/kodi.h b/kodi/kodi.h index 46db56d9..91ea9d2a 100644 --- a/kodi/kodi.h +++ b/kodi/kodi.h @@ -29,12 +29,15 @@ #include "kodiconnection.h" #include "kodijsonhandler.h" +#include "types/browseritem.h" +#include "devices/device.h" + class Kodi : public QObject { Q_OBJECT public: - explicit Kodi(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr); + explicit Kodi(const QHostAddress &hostAddress, int port = 9090, int httpPort = 8080, QObject *parent = nullptr); QHostAddress hostAddress() const; int port() const; @@ -64,6 +67,10 @@ public: void connectKodi(); void disconnectKodi(); + Device::BrowseResult browse(const QString &itemId, Device::BrowseResult &result); + Device::BrowserItemResult browserItem(const QString &itemId, Device::BrowserItemResult &result); + Device::DeviceError launchBrowserItem(const QString &itemId); + signals: void connectionStatusChanged(); void stateChanged(); @@ -75,6 +82,8 @@ signals: void mediaMetadataChanged(const QString &title, const QString &artist, const QString &collection, const QString &artwork); void shuffleChanged(bool shuffle); void repeatChanged(const QString &repeat); + void browseResult(const Device::BrowseResult &result); + void browserItemResult(const Device::BrowserItemResult &result); private slots: void onVolumeChanged(const int &volume, const bool &muted); @@ -90,14 +99,40 @@ private slots: void updatePlayerProperties(); void updateMetadata(); +private: + QString prepareThumbnail(const QString &thumbnail); + private: KodiConnection *m_connection; + int m_httpPort; KodiJsonHandler *m_jsonHandler; bool m_muted; int m_volume; int m_activePlayerCount = 0; // if it's > 0, there is something playing (either music or video or slideshow) int m_activePlayer = -1; + class VirtualFsNode { + public: + VirtualFsNode(const BrowserItem &item):item(item) {} + BrowserItem item; + QList childs; + QString getMethod; + QVariantMap getParams; + void addChild(VirtualFsNode* child) {childs.append(child); } + VirtualFsNode *findNode(const QString &id) { + if (item.id() == id) return this; + foreach (VirtualFsNode *child, childs) { + VirtualFsNode *node = child->findNode(id); + if (node) return node; + } + return nullptr; + } + }; + VirtualFsNode* m_virtualFs = nullptr; + + QHash m_pendingBrowseRequests; + QHash m_pendingBrowserItemRequests; + }; #endif // KODI_H diff --git a/kodi/kodiconnection.cpp b/kodi/kodiconnection.cpp index d1994b2f..655dcdd7 100644 --- a/kodi/kodiconnection.cpp +++ b/kodi/kodiconnection.cpp @@ -26,7 +26,7 @@ #include -KodiConnection::KodiConnection(const QHostAddress &hostAddress, const int &port, QObject *parent) : +KodiConnection::KodiConnection(const QHostAddress &hostAddress, int port, QObject *parent) : QObject(parent), m_hostAddress(hostAddress), m_port(port), diff --git a/kodi/kodiconnection.h b/kodi/kodiconnection.h index cb633c48..164f738d 100644 --- a/kodi/kodiconnection.h +++ b/kodi/kodiconnection.h @@ -32,13 +32,14 @@ class KodiConnection : public QObject { Q_OBJECT public: - explicit KodiConnection(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr); + explicit KodiConnection(const QHostAddress &hostAddress, int port = 9090, QObject *parent = nullptr); void connectKodi(); void disconnectKodi(); QHostAddress hostAddress() const; int port() const; + int httpPort() const; bool connected(); diff --git a/kodi/kodijsonhandler.cpp b/kodi/kodijsonhandler.cpp index 3ad6944b..87194a2e 100644 --- a/kodi/kodijsonhandler.cpp +++ b/kodi/kodijsonhandler.cpp @@ -53,14 +53,29 @@ int KodiJsonHandler::sendData(const QString &method, const QVariantMap ¶ms) void KodiJsonHandler::processResponse(const QByteArray &data) { + m_dataBuffer.append(data); + + QByteArray packet = m_dataBuffer; + int pos = packet.indexOf("}{"); + if (pos > 0) { + packet = m_dataBuffer.left(pos + 1); + } + + if (!packet.endsWith("}")) { + return; // Won't parse for sure, likely not enough data yet + } + QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QJsonDocument jsonDoc = QJsonDocument::fromJson(packet, &error); if(error.error != QJsonParseError::NoError) { qCWarning(dcKodi) << "failed to parse JSON data:" << data << ":" << error.errorString(); return; } + // Ok, we managed to parse a complete packet, remove it from the input buffer + m_dataBuffer.remove(0, packet.length()); + //qCDebug(dcKodi) << "data received:" << jsonDoc.toJson(); QVariantMap message = jsonDoc.toVariant().toMap(); diff --git a/kodi/kodijsonhandler.h b/kodi/kodijsonhandler.h index 947b20a8..b57682d0 100644 --- a/kodi/kodijsonhandler.h +++ b/kodi/kodijsonhandler.h @@ -50,6 +50,7 @@ private: KodiConnection *m_connection; int m_id; QHash m_replys; + QByteArray m_dataBuffer; }; From 37909b1d2f2b181f16470c782913614e7081bcd1 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Jul 2019 01:41:07 +0200 Subject: [PATCH 2/3] Make update/clean library browserItemActions --- kodi/devicepluginkodi.cpp | 104 ++++++++++++--- kodi/devicepluginkodi.h | 4 +- kodi/devicepluginkodi.json | 63 ++++------ kodi/kodi.cpp | 250 +++++++++++++++++++++++++------------ kodi/kodi.h | 9 +- 5 files changed, 296 insertions(+), 134 deletions(-) diff --git a/kodi/devicepluginkodi.cpp b/kodi/devicepluginkodi.cpp index 30eadd35..6799ff93 100644 --- a/kodi/devicepluginkodi.cpp +++ b/kodi/devicepluginkodi.cpp @@ -76,6 +76,7 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) connect(kodi, &Kodi::playbackStatusChanged, this, &DevicePluginKodi::onPlaybackStatusChanged); connect(kodi, &Kodi::browseResult, this, &DevicePluginKodi::browseRequestFinished); connect(kodi, &Kodi::browserItemResult, this, &DevicePluginKodi::browserItemRequestFinished); + connect(kodi, &Kodi::browserItemActionExecuted, this, &DevicePluginKodi::onBrowserItemActionExecuted); connect(kodi, &Kodi::activePlayerChanged, device, [device](const QString &playerType){ device->setStateValue(kodiPlayerTypeStateTypeId, playerType); @@ -94,8 +95,9 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) } else { addr = "[" + hostAddr.toString() + "]"; } + QString port = device->paramValue(kodiDeviceHttpPortParamTypeId).toString(); - request.setUrl(QUrl("http://" + addr + ":8080/jsonrpc")); + request.setUrl(QUrl(QString("http://%1:%2/jsonrpc").arg(addr).arg(port))); qCDebug(dcKodi) << "Prepping file dl" << "http://" + addr + ":" + device->paramValue(kodiDevicePortParamTypeId).toString() + "/jsonrpc"; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QVariantMap map; @@ -107,10 +109,10 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) map.insert("params", params); QJsonDocument jsonDoc = QJsonDocument::fromVariant(map); QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson(QJsonDocument::Compact)); - connect(reply, &QNetworkReply::finished, device, [device, reply, addr](){ + connect(reply, &QNetworkReply::finished, device, [device, reply, addr, port](){ reply->deleteLater(); QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); - QString fileUrl = "http://" + addr + ":8080/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString(); + QString fileUrl = "http://" + addr + ":" + port + "/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString(); qCDebug(dcKodi()) << "DL result:" << jsonDoc.toJson(); qCDebug(dcKodi()) << "Resolved url:" << fileUrl; device->setStateValue(kodiArtworkStateTypeId, fileUrl); @@ -152,19 +154,68 @@ Device::DeviceError DevicePluginKodi::discoverDevices(const DeviceClassId &devic Q_UNUSED(deviceClassId) ZeroConfServiceBrowser *serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp"); - QTimer::singleShot(5000, this, [this, serviceBrowser](){ - QList descriptors; + ZeroConfServiceBrowser *httpServiceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp"); + QTimer::singleShot(5000, this, [this, serviceBrowser, httpServiceBrowser](){ + QHash descriptors; + foreach (const ZeroConfServiceEntry avahiEntry, serviceBrowser->serviceEntries()) { + + QString uuid; + foreach (const QString &txt, avahiEntry.txt()) { + if (txt.startsWith("uuid")) { + uuid = txt.split("=").last(); + break; + } + } + + if (descriptors.contains(uuid)) { + // Might appear multiple times, IPv4 and IPv6 + continue; + } + qCDebug(dcKodi) << "Zeroconf entry:" << avahiEntry; DeviceDescriptor descriptor(kodiDeviceClassId, avahiEntry.name(), avahiEntry.hostName() + " (" + avahiEntry.hostAddress().toString() + ")"); ParamList params; params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString()); params << Param(kodiDevicePortParamTypeId, avahiEntry.port()); + params << Param(kodiDeviceUuidParamTypeId, uuid); descriptor.setParams(params); - descriptors << descriptor; + + Devices existing = myDevices().filterByParam(kodiDeviceUuidParamTypeId, uuid); + if (existing.count() > 0) { + descriptor.setDeviceId(existing.first()->id()); + } + + descriptors.insert(uuid, descriptor); } - emit devicesDiscovered(kodiDeviceClassId, descriptors); + + foreach (const ZeroConfServiceEntry avahiEntry, httpServiceBrowser->serviceEntries()) { +// qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry; + QString uuid; + foreach (const QString &txt, avahiEntry.txt()) { + if (txt.startsWith("uuid")) { + uuid = txt.split("=").last(); + break; + } + } + if (!descriptors.contains(uuid)) { + continue; + } + qCDebug(dcKodi()) << "Updating http parameter:" << avahiEntry.port(); + DeviceDescriptor descriptor = descriptors.value(uuid); + ParamList params = descriptor.params(); + params << Param(kodiDeviceHttpPortParamTypeId, avahiEntry.port()); + descriptor.setParams(params); + descriptors[uuid] = descriptor; + } + + + foreach (const DeviceDescriptor &d, descriptors.values()) { + qCDebug(dcKodi()) << "Returning descritpor:" << d.params(); + } + emit devicesDiscovered(kodiDeviceClassId, descriptors.values()); serviceBrowser->deleteLater(); + httpServiceBrowser->deleteLater(); }); return Device::DeviceErrorAsync; @@ -181,8 +232,12 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action } int commandId = -1; - if (action.actionTypeId() == kodiShowNotificationActionTypeId) { - commandId = kodi->showNotification(action.param(kodiShowNotificationActionMessageParamTypeId).value().toString(), 8000, action.param(kodiShowNotificationActionTypeParamTypeId).value().toString()); + if (action.actionTypeId() == kodiNotifyActionTypeId) { + commandId = kodi->showNotification( + action.param(kodiNotifyActionTitleParamTypeId).value().toString(), + action.param(kodiNotifyActionBodyParamTypeId).value().toString(), + 8000, + action.param(kodiNotifyActionTypeParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiVolumeActionTypeId) { commandId = kodi->setVolume(action.param(kodiVolumeActionVolumeParamTypeId).value().toInt()); } else if (action.actionTypeId() == kodiMuteActionTypeId) { @@ -191,10 +246,6 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action commandId = kodi->pressButton(action.param(kodiPressButtonActionButtonParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiSystemActionTypeId) { commandId = kodi->systemCommand(action.param(kodiSystemActionSystemCommandParamTypeId).value().toString()); - } else if (action.actionTypeId() == kodiVideoLibraryActionTypeId) { - commandId = kodi->videoLibrary(action.param(kodiVideoLibraryActionVideoCommandParamTypeId).value().toString()); - } else if (action.actionTypeId() == kodiAudioLibraryActionTypeId) { - commandId = kodi->audioLibrary(action.param(kodiAudioLibraryActionAudioCommandParamTypeId).value().toString()); } else if(action.actionTypeId() == kodiSkipBackActionTypeId) { commandId = kodi->pressButton("skipprevious"); } else if(action.actionTypeId() == kodiFastRewindActionTypeId) { @@ -267,6 +318,21 @@ Device::DeviceError DevicePluginKodi::executeBrowserItem(Device *device, const B return kodi->launchBrowserItem(browserAction.itemId()); } +Device::DeviceError DevicePluginKodi::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) +{ + Kodi *kodi = m_kodis.key(device); + if (!kodi) { + return Device::DeviceErrorHardwareNotAvailable; + } + + int id = kodi->executeBrowserItemAction(browserItemAction.itemId(), browserItemAction.actionTypeId()); + if (id == -1) { + return Device::DeviceErrorHardwareFailure; + } + m_pendingBrowserItemActions.insert(id, browserItemAction.id()); + return Device::DeviceErrorAsync; +} + void DevicePluginKodi::onPluginTimer() { foreach (Kodi *kodi, m_kodis.keys()) { @@ -308,7 +374,15 @@ void DevicePluginKodi::onActionExecuted(int actionId, bool success) if (!m_pendingActions.contains(actionId)) { return; } - emit actionExecutionFinished(m_pendingActions.value(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorInvalidParameter); + emit actionExecutionFinished(m_pendingActions.take(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorInvalidParameter); +} + +void DevicePluginKodi::onBrowserItemActionExecuted(int actionId, bool success) +{ + if (!m_pendingBrowserItemActions.contains(actionId)) { + return; + } + emit browserItemActionExecutionFinished(m_pendingBrowserItemActions.take(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure); } void DevicePluginKodi::versionDataReceived(const QVariantMap &data) @@ -339,7 +413,7 @@ void DevicePluginKodi::onSetupFinished(const QVariantMap &data) emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess); - kodi->showNotification("Connected", 2000, "info"); + kodi->showNotification("nymea", tr("Connected"), 2000, "info"); } void DevicePluginKodi::onPlaybackStatusChanged(const QString &playbackStatus) diff --git a/kodi/devicepluginkodi.h b/kodi/devicepluginkodi.h index 3b6ccc64..5aa3fb50 100644 --- a/kodi/devicepluginkodi.h +++ b/kodi/devicepluginkodi.h @@ -50,7 +50,7 @@ public: Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override; Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) override; Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction) override; -// Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override; + Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override; private: PluginTimer *m_pluginTimer; @@ -58,12 +58,14 @@ private: QList m_asyncSetups; QHash m_pendingActions; + QHash m_pendingBrowserItemActions; private slots: void onPluginTimer(); void onConnectionChanged(); void onStateChanged(); void onActionExecuted(int actionId, bool success); + void onBrowserItemActionExecuted(int actionId, bool success); void versionDataReceived(const QVariantMap &data); void onSetupFinished(const QVariantMap &data); diff --git a/kodi/devicepluginkodi.json b/kodi/devicepluginkodi.json index 34412b63..06ab1f25 100644 --- a/kodi/devicepluginkodi.json +++ b/kodi/devicepluginkodi.json @@ -12,7 +12,7 @@ "id": "d09953e3-c5bd-415b-973b-0d0bf2be3f69", "name": "kodi", "displayName": "Kodi", - "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"], + "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "notifications", "connectable"], "createMethods": ["user", "discovery"], "browsable": true, "paramTypes": [ @@ -35,6 +35,12 @@ "displayName": "HTTP port", "type" : "int", "defaultValue": 8080 + }, + { + "id": "692eb6e0-7f4e-4f43-92da-8347372287ce", + "name": "uuid", + "displayName": "UUID", + "type": "QString" } ], "stateTypes": [ @@ -198,12 +204,19 @@ }, { "id": "dc0aa3b5-4eae-4e58-a4ac-d4c124da53f1", - "name": "showNotification", + "name": "notify", "displayName": "show notification", "paramTypes": [ { "id": "798f720a-cc4f-40e7-91d7-2ef5957ca7ad", - "name": "message", + "name": "title", + "displayName": "title", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "c92d79ad-3b74-4cb6-a21b-d6a0a3cfd3e1", + "name": "body", "displayName": "message", "type": "QString", "inputType": "TextLine" @@ -286,42 +299,18 @@ ] } ] + } + ], + "browserItemActionTypes": [ + { + "id": "3fed69c5-dddf-4500-a674-c79015f63974", + "name": "updateLibrary", + "displayName": "Update library" }, { - "id": "59f7ad10-16eb-40b7-a88b-c8393ae8e413", - "name": "videoLibrary", - "displayName": "video library", - "paramTypes": [ - { - "id": "3219855b-e043-43aa-91ae-794b474379bf", - "name": "videoCommand", - "displayName": "command", - "type": "QString", - "defaultValue": "scan", - "allowedValues": [ - "scan", - "clean" - ] - } - ] - }, - { - "id": "3d5120eb-1007-46c3-b76e-d9d4b105b9f2", - "name": "audioLibrary", - "displayName": "audio library", - "paramTypes": [ - { - "id": "ba89d098-56d7-40a2-b499-c02499c1ec0c", - "name": "audioCommand", - "displayName": "command", - "type": "QString", - "defaultValue": "scan", - "allowedValues": [ - "scan", - "clean" - ] - } - ] + "id": "dbc2c455-ae75-493e-9d8b-659e951b55a1", + "name": "cleanLibrary", + "displayName": "Clean library" } ] } diff --git a/kodi/kodi.cpp b/kodi/kodi.cpp index 13ac75fe..ac5d2ad4 100644 --- a/kodi/kodi.cpp +++ b/kodi/kodi.cpp @@ -24,6 +24,7 @@ #include #include "extern-plugininfo.h" #include +#include Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *parent) : QObject(parent), @@ -49,44 +50,11 @@ Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *par QVariantList properties; properties.append("thumbnail"); - // Music - BrowserItem item = BrowserItem("audiolibrary", tr("Music library"), true); - item.setDescription(tr("")); - item.setIcon(BrowserItem::BrowserIconFolder); - VirtualFsNode *audioLibrary = new VirtualFsNode(item); - m_virtualFs->addChild(audioLibrary); - - item = BrowserItem("artists", tr("Artists"), true); - item.setDescription(tr("")); - item.setIcon(BrowserItem::BrowserIconFolder); - VirtualFsNode *artists = new VirtualFsNode(item); - artists->getMethod = "AudioLibrary.GetArtists"; - artists->getParams.insert("sort", sort); - artists->getParams.insert("properties", properties); - audioLibrary->addChild(artists); - - item = BrowserItem("albums", tr("Albums"), true); - item.setDescription(tr("")); - item.setIcon(BrowserItem::BrowserIconFolder); - VirtualFsNode *albums = new VirtualFsNode(item); - albums->getMethod = "AudioLibrary.GetAlbums"; - albums->getParams.insert("sort", sort); - albums->getParams.insert("properties", properties); - audioLibrary->addChild(albums); - - item = BrowserItem("songs", tr("Songs"), true); - item.setDescription(tr("")); - item.setIcon(BrowserItem::BrowserIconFolder); - VirtualFsNode *songs = new VirtualFsNode(item); - songs->getMethod = "AudioLibrary.GetSongs"; - songs->getParams.insert("sort", sort); - songs->getParams.insert("properties", properties); - audioLibrary->addChild(songs); - // Video - item = BrowserItem("videolibrary", tr("Video library"), true); + BrowserItem item = BrowserItem("videolibrary", tr("Video library"), true); item.setDescription(tr("")); item.setIcon(BrowserItem::BrowserIconFolder); + item.setActionTypeIds({kodiUpdateLibraryBrowserItemActionTypeId, kodiCleanLibraryBrowserItemActionTypeId}); VirtualFsNode *videoLibrary = new VirtualFsNode(item); m_virtualFs->addChild(videoLibrary); @@ -96,7 +64,11 @@ Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *par VirtualFsNode *movies = new VirtualFsNode(item); movies->getMethod = "VideoLibrary.GetMovies"; movies->getParams.insert("sort", sort); - movies->getParams.insert("properties", properties); + QVariantList movieProperties = properties; + movieProperties.append("year"); + movieProperties.append("rating"); + movieProperties.append("runtime"); + movies->getParams.insert("properties", movieProperties); videoLibrary->addChild(movies); item = BrowserItem("tvshows", tr("TV Shows"), true); @@ -105,7 +77,11 @@ Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *par VirtualFsNode *tvShows = new VirtualFsNode(item); tvShows->getMethod = "VideoLibrary.GetTVShows"; tvShows->getParams.insert("sort", sort); - tvShows->getParams.insert("properties", properties); + QVariantList tvShowProperties = properties; + tvShowProperties.append("year"); + tvShowProperties.append("rating"); + tvShowProperties.append("season"); + tvShows->getParams.insert("properties", tvShowProperties); videoLibrary->addChild(tvShows); item = BrowserItem("musicvideos", tr("Music Videos"), true); @@ -117,6 +93,52 @@ Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *par musicVideos->getParams.insert("properties", properties); videoLibrary->addChild(musicVideos); + // Music + item = BrowserItem("audiolibrary", tr("Music library"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setActionTypeIds({kodiUpdateLibraryBrowserItemActionTypeId, kodiCleanLibraryBrowserItemActionTypeId}); + VirtualFsNode *audioLibrary = new VirtualFsNode(item); + m_virtualFs->addChild(audioLibrary); + + item = BrowserItem("artists", tr("Artists"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *artists = new VirtualFsNode(item); + artists->getMethod = "AudioLibrary.GetArtists"; + artists->getParams.insert("sort", sort); + QVariantList artistProperties = properties; + artistProperties.append("formed"); + artistProperties.append("genre"); + artists->getParams.insert("properties", artistProperties); + audioLibrary->addChild(artists); + + item = BrowserItem("albums", tr("Albums"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *albums = new VirtualFsNode(item); + albums->getMethod = "AudioLibrary.GetAlbums"; + albums->getParams.insert("sort", sort); + QVariantList albumProperties = properties; + albumProperties.append("artist"); + albumProperties.append("year"); + albums->getParams.insert("properties", albumProperties); + audioLibrary->addChild(albums); + + item = BrowserItem("songs", tr("Songs"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconFolder); + VirtualFsNode *songs = new VirtualFsNode(item); + songs->getMethod = "AudioLibrary.GetSongs"; + songs->getParams.insert("sort", sort); + QVariantList songProperties = properties; + songProperties.append("artist"); + songProperties.append("album"); + songProperties.append("year"); + songs->getParams.insert("properties", songProperties); + audioLibrary->addChild(songs); + + // Add-ons item = BrowserItem("addons", tr("Add-ons"), true); item.setDescription(tr("")); item.setIcon(BrowserItem::BrowserIconFolder); @@ -203,10 +225,10 @@ int Kodi::setRepeat(const QString &repeat) return m_jsonHandler->sendData("Player.SetRepeat", params); } -int Kodi::showNotification(const QString &message, const int &displayTime, const QString ¬ificationType) +int Kodi::showNotification(const QString &title, const QString &message, const int &displayTime, const QString ¬ificationType) { QVariantMap params; - params.insert("title", "nymea notification"); + params.insert("title", title); params.insert("message", message); params.insert("displaytime", displayTime); params.insert("image", notificationType); @@ -239,34 +261,6 @@ int Kodi::systemCommand(const QString &command) return m_jsonHandler->sendData("System." + method, QVariantMap()); } -int Kodi::videoLibrary(const QString &command) -{ - QString method; - if (command == "scan") { - method = "Scan"; - } else if (command == "clean") { - method = "Clean"; - } else { - // already checkt with allowed values - } - - return m_jsonHandler->sendData("VideoLibrary." + method, QVariantMap()); -} - -int Kodi::audioLibrary(const QString &command) -{ - QString method; - if (command == "scan") { - method = "Scan"; - } else if (command == "clean") { - method = "Clean"; - } else { - // already checkt with allowed values - } - - return m_jsonHandler->sendData("AudioLibrary." + method, QVariantMap()); -} - void Kodi::update() { QVariantMap params; @@ -331,7 +325,10 @@ Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &r filter.insert("artistid", idString.toInt()); QVariantMap params; params.insert("filter", filter); - params.insert("properties", properties); + QVariantList albumProperties = properties; + albumProperties.append("artist"); + albumProperties.append("year"); + params.insert("properties", albumProperties); int id = m_jsonHandler->sendData("AudioLibrary.GetAlbums", params); m_pendingBrowseRequests.insert(id, result); result.status = Device::DeviceErrorAsync; @@ -345,10 +342,12 @@ Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &r filter.insert("albumid", idString.toInt()); QVariantMap params; params.insert("filter", filter); - QVariantList properties; - properties.append("thumbnail"); - properties.append("albumid"); - params.insert("properties", properties); + QVariantList songProperties = properties; + songProperties.append("albumid"); + songProperties.append("artist"); + songProperties.append("album"); + songProperties.append("year"); + params.insert("properties", songProperties); int id = m_jsonHandler->sendData("AudioLibrary.GetSongs", params); m_pendingBrowseRequests.insert(id, result); result.status = Device::DeviceErrorAsync; @@ -364,6 +363,7 @@ Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &r properties.append("tvshowid"); properties.append("season"); properties.append("thumbnail"); + properties.append("showtitle"); params.insert("properties", properties); int id = m_jsonHandler->sendData("VideoLibrary.GetSeasons", params); m_pendingBrowseRequests.insert(id, result); @@ -380,6 +380,10 @@ Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &r QVariantMap params; params.insert("tvshowid", tvShowId); params.insert("season", seasonId); + QVariantList properties; + properties.append("thumbnail"); + properties.append("showtitle"); + properties.append("season"); params.insert("properties", properties); qCDebug(dcKodi()) << "getting episodes:" << params; int id = m_jsonHandler->sendData("VideoLibrary.GetEpisodes", params); @@ -505,6 +509,29 @@ Device::DeviceError Kodi::launchBrowserItem(const QString &itemId) return Device::DeviceErrorNoError; } +int Kodi::executeBrowserItemAction(const QString &itemId, const ActionTypeId &actionTypeId) +{ + QString scope; + QString method; + if (actionTypeId == kodiUpdateLibraryBrowserItemActionTypeId) { + method = "Scan"; + } else if (actionTypeId == kodiCleanLibraryBrowserItemActionTypeId) { + method = "Clean"; + } else { + return -1; + } + + if (itemId == "audiolibrary") { + scope = "AudioLibrary"; + } else if (itemId == "videolibrary") { + scope = "VideoLibrary"; + } else { + return -1; + } + + return m_jsonHandler->sendData(scope + "." + method, QVariantMap()); +} + void Kodi::onVolumeChanged(const int &volume, const bool &muted) { if (m_volume != volume || m_muted != muted) { @@ -623,10 +650,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res qCDebug(dcKodi) << "response received:" << method << response; if (response.contains("error")) { - //qCDebug(dcKodi) << QJsonDocument::fromVariant(response).toJson(); qCWarning(dcKodi) << "got error response for request " << method << ":" << response.value("error").toMap().value("message").toString(); - emit actionExecuted(id, false); - return; } if (method == "Application.GetProperties") { @@ -661,7 +685,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res if (method == "Player.SetShuffle" || method == "Player.SetRepeat") { updatePlayerProperties(); - emit actionExecuted(id, true); + emit actionExecuted(id, !response.contains("error")); return; } @@ -674,6 +698,14 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setBrowsable(true); item.setIcon(BrowserItem::BrowserIconFolder); item.setThumbnail(prepareThumbnail(artist.value("thumbnail").toString())); + QStringList description; + if (!artist.value("formed").toString().isEmpty()) { + description.append(artist.value("formed").toString()); + } + if (!artist.value("genre").toStringList().isEmpty()) { + description.append(artist.value("genre").toStringList().join(", ")); + } + item.setDescription(description.join(" - ")); qCDebug(dcKodi()) << "Thumbnail" << item.thumbnail(); result.items.append(item); } @@ -689,6 +721,14 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setBrowsable(true); item.setIcon(BrowserItem::BrowserIconFolder); item.setThumbnail(prepareThumbnail(album.value("thumbnail").toString())); + QStringList description; + if (!album.value("artist").toStringList().isEmpty()) { + description.append(album.value("artist").toStringList().join(", ")); + } + if (album.value("year").toInt() != 0) { + description.append(album.value("year").toString()); + } + item.setDescription(description.join(" - ")); result.items.append(item); } emit browseResult(result); @@ -712,6 +752,16 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setExecutable(true); item.setIcon(BrowserItem::BrowserIconMusic); item.setThumbnail(prepareThumbnail(song.value("thumbnail").toString())); + QStringList description; + if (!song.value("artist").toStringList().isEmpty()) { + description.append(song.value("artist").toStringList().join(",")); + } + if (!song.value("album").toString().isEmpty()) { + description.append(song.value("album").toString()); + } else if (!song.value("year").toString().isEmpty()) { + description.append(song.value("year").toString()); + } + item.setDescription(description.join(" - ")); result.items.append(item); i++; } @@ -729,6 +779,21 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setExecutable(true); item.setIcon(BrowserItem::BrowserIconVideo); item.setThumbnail(prepareThumbnail(movie.value("thumbnail").toString())); + QString rating; + for (int i = 0; i < 5; i++) { + if (qRound(movie.value("rating").toDouble() / 2) >= i) { + rating += "★"; + } else { + rating += "☆"; + } + } + + int runtime = movie.value("runtime").toInt(); + int hours = runtime / 60 / 60; + int minutes = (runtime / 60) % 60; + QString duration; + duration = QString("%1:%2").arg(hours).arg(minutes, 2, 10, QChar('0')); + item.setDescription(movie.value("year").toString() + " - " + duration + " - " + rating); result.items.append(item); } emit browseResult(result); @@ -744,6 +809,15 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setBrowsable(true); item.setIcon(BrowserItem::BrowserIconFolder); item.setThumbnail(prepareThumbnail(tvShow.value("thumbnail").toString())); + QString rating; + for (int i = 0; i < 5; i++) { + if (qRound(tvShow.value("rating").toDouble() / 2) >= i) { + rating += "★"; + } else { + rating += "☆"; + } + } + item.setDescription(tvShow.value("year").toString() + " - " + tr("%1 seasons").arg(tvShow.value("season").toInt()) + " - " + rating); result.items.append(item); } emit browseResult(result); @@ -759,6 +833,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setBrowsable(true); item.setIcon(BrowserItem::BrowserIconFolder); item.setThumbnail(prepareThumbnail(season.value("thumbnail").toString())); + item.setDescription(season.value("showtitle").toString()); result.items.append(item); } emit browseResult(result); @@ -774,6 +849,11 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setExecutable(true); item.setIcon(BrowserItem::BrowserIconVideo); item.setThumbnail(prepareThumbnail(episode.value("thumbnail").toString())); + if (!episode.value("season").toString().isEmpty()) { + item.setDescription(episode.value("showtitle").toString() + " - " + tr("Season %1").arg(episode.value("season").toString())); + } else { + item.setDescription(episode.value("showtitle").toString()); + } result.items.append(item); } emit browseResult(result); @@ -803,6 +883,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res BrowserItem item("addon:" + addon.value("addonid").toString(), addon.value("name").toString()); item.setBrowsable(true); item.setIcon(BrowserItem::BrowserIconApplication); + item.setThumbnail(prepareThumbnail(addon.value("thumbnail").toString())); result.items.append(item); } emit browseResult(result); @@ -825,6 +906,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res item.setExecutable(true); item.setIcon(BrowserItem::BrowserIconMusic); } + item.setThumbnail(prepareThumbnail(file.value("thumbnail").toString())); result.items.append(item); } emit browseResult(result); @@ -867,6 +949,16 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res return; } + if (method == "GUI.ShowNotification") { + emit actionExecuted(id, !response.contains("error")); + return; + } + + if (method == "VideoLibrary.Scan" || method == "VideoLibrary.Clean" || method == "AudioLibrary.Scan" || method == "AudioLibrary.Clean") { + emit browserItemActionExecuted(id, !response.contains("error")); + return; + } + qCWarning(dcKodi()) << "unhandled reply" << method << response; } @@ -896,8 +988,12 @@ QString Kodi::prepareThumbnail(const QString &thumbnail) return QString(); } + QString addr = m_connection->hostAddress().toString(); + if (m_connection->hostAddress().protocol() == QAbstractSocket::IPv6Protocol) { + addr = '[' + addr + ']'; + } return QString("http://%1:%2/image/%3") - .arg(m_connection->hostAddress().toString()) + .arg(addr) .arg(m_httpPort) .arg(QString(thumbnail.toUtf8().toPercentEncoding())); } diff --git a/kodi/kodi.h b/kodi/kodi.h index 91ea9d2a..830dcb2e 100644 --- a/kodi/kodi.h +++ b/kodi/kodi.h @@ -30,6 +30,7 @@ #include "kodijsonhandler.h" #include "types/browseritem.h" +#include "types/browseritemaction.h" #include "devices/device.h" class Kodi : public QObject @@ -55,11 +56,9 @@ public: int setRepeat(const QString &repeat); // actions - int showNotification(const QString &message, const int &displayTime, const QString ¬ificationType); + int showNotification(const QString &title, const QString &message, const int &displayTime, const QString ¬ificationType); int pressButton(const QString &button); int systemCommand(const QString &command); - int videoLibrary(const QString &command); - int audioLibrary(const QString &command); void update(); void checkVersion(); @@ -70,12 +69,13 @@ public: Device::BrowseResult browse(const QString &itemId, Device::BrowseResult &result); Device::BrowserItemResult browserItem(const QString &itemId, Device::BrowserItemResult &result); Device::DeviceError launchBrowserItem(const QString &itemId); + int executeBrowserItemAction(const QString &itemId, const ActionTypeId &actionTypeId); signals: void connectionStatusChanged(); void stateChanged(); void activePlayerChanged(const QString &playerType); - void actionExecuted(int actionId, const bool &success); + void actionExecuted(int actionId, bool success); void updateDataReceived(const QVariantMap &data); void versionDataReceived(const QVariantMap &data); void playbackStatusChanged(const QString &playbackState); @@ -84,6 +84,7 @@ signals: void repeatChanged(const QString &repeat); void browseResult(const Device::BrowseResult &result); void browserItemResult(const Device::BrowserItemResult &result); + void browserItemActionExecuted(int actionId, bool success); private slots: void onVolumeChanged(const int &volume, const bool &muted); From 8fd164427f50cae2e93ff8e080ae2cd9ac7f9a57 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Jul 2019 16:25:14 +0200 Subject: [PATCH 3/3] Add support for the navigationpad interface --- kodi/devicepluginkodi.cpp | 18 ++++++++--------- kodi/devicepluginkodi.json | 41 ++++++++++---------------------------- kodi/kodi.cpp | 15 +++++++++++--- kodi/kodi.h | 2 +- 4 files changed, 32 insertions(+), 44 deletions(-) diff --git a/kodi/devicepluginkodi.cpp b/kodi/devicepluginkodi.cpp index 6799ff93..9d8f4b3d 100644 --- a/kodi/devicepluginkodi.cpp +++ b/kodi/devicepluginkodi.cpp @@ -242,24 +242,24 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action commandId = kodi->setVolume(action.param(kodiVolumeActionVolumeParamTypeId).value().toInt()); } else if (action.actionTypeId() == kodiMuteActionTypeId) { commandId = kodi->setMuted(action.param(kodiMuteActionMuteParamTypeId).value().toBool()); - } else if (action.actionTypeId() == kodiPressButtonActionTypeId) { - commandId = kodi->pressButton(action.param(kodiPressButtonActionButtonParamTypeId).value().toString()); + } else if (action.actionTypeId() == kodiNavigateActionTypeId) { + commandId = kodi->navigate(action.param(kodiNavigateActionToParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiSystemActionTypeId) { commandId = kodi->systemCommand(action.param(kodiSystemActionSystemCommandParamTypeId).value().toString()); } else if(action.actionTypeId() == kodiSkipBackActionTypeId) { - commandId = kodi->pressButton("skipprevious"); + commandId = kodi->navigate("skipprevious"); } else if(action.actionTypeId() == kodiFastRewindActionTypeId) { - commandId = kodi->pressButton("rewind"); + commandId = kodi->navigate("rewind"); } else if(action.actionTypeId() == kodiStopActionTypeId) { - commandId = kodi->pressButton("stop"); + commandId = kodi->navigate("stop"); } else if(action.actionTypeId() == kodiPlayActionTypeId) { - commandId = kodi->pressButton("play"); + commandId = kodi->navigate("play"); } else if(action.actionTypeId() == kodiPauseActionTypeId) { - commandId = kodi->pressButton("pause"); + commandId = kodi->navigate("pause"); } else if(action.actionTypeId() == kodiFastForwardActionTypeId) { - commandId = kodi->pressButton("fastforward"); + commandId = kodi->navigate("fastforward"); } else if(action.actionTypeId() == kodiSkipNextActionTypeId) { - commandId = kodi->pressButton("skipnext"); + commandId = kodi->navigate("skipnext"); } else if (action.actionTypeId() == kodiShuffleActionTypeId) { commandId = kodi->setShuffle(action.param(kodiShuffleActionShuffleParamTypeId).value().toBool()); } else if (action.actionTypeId() == kodiRepeatActionTypeId) { diff --git a/kodi/devicepluginkodi.json b/kodi/devicepluginkodi.json index 06ab1f25..241eeb1a 100644 --- a/kodi/devicepluginkodi.json +++ b/kodi/devicepluginkodi.json @@ -12,7 +12,7 @@ "id": "d09953e3-c5bd-415b-973b-0d0bf2be3f69", "name": "kodi", "displayName": "Kodi", - "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "notifications", "connectable"], + "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "notifications", "extendednavigationpad", "connectable"], "createMethods": ["user", "discovery"], "browsable": true, "paramTypes": [ @@ -237,45 +237,24 @@ }, { "id": "28060803-aa85-44a4-9dec-ee669dfb629f", - "name": "pressButton", - "displayName": "press button", + "name": "navigate", + "displayName": "Navigate", "paramTypes": [ { "id": "93861dac-0c24-4a3b-903d-d1be44eae611", - "name": "button", - "displayName": "button", + "name": "to", + "displayName": "to", "type": "QString", "allowedValues": [ - "left", - "right", "up", "down", - "pageup", - "pagedown", - "select", + "left", + "right", + "enter", "back", + "menu", "info", - "pause", - "stop", - "skipnext", - "skipprevious", - "stepforward", - "stepback", - "osd", - "play", - "playpause", - "fastforward", - "rewind", - "fullscreen", - "mute", - "volumeup", - "volumedown", - "channelup", - "channeldown", - "red", - "green", - "yellow", - "blue" + "home" ] } ] diff --git a/kodi/kodi.cpp b/kodi/kodi.cpp index ac5d2ad4..03a2a0c8 100644 --- a/kodi/kodi.cpp +++ b/kodi/kodi.cpp @@ -236,10 +236,19 @@ int Kodi::showNotification(const QString &title, const QString &message, const i return m_jsonHandler->sendData("GUI.ShowNotification", params); } -int Kodi::pressButton(const QString &button) +int Kodi::navigate(const QString &to) { + qCDebug(dcKodi()) << "Navigate:" << to; + if (to == "home") { + return m_jsonHandler->sendData("Input.Home", QVariantMap()); + } + QVariantMap params; - params.insert("action", button); + QString mappedTo = to; + if (to == "enter") { + mappedTo = "select"; + } + params.insert("action", mappedTo); return m_jsonHandler->sendData("Input.ExecuteAction", params); } @@ -949,7 +958,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res return; } - if (method == "GUI.ShowNotification") { + if (method == "GUI.ShowNotification" || method == "Input.ExecuteAction") { emit actionExecuted(id, !response.contains("error")); return; } diff --git a/kodi/kodi.h b/kodi/kodi.h index 830dcb2e..7de08831 100644 --- a/kodi/kodi.h +++ b/kodi/kodi.h @@ -57,7 +57,7 @@ public: // actions int showNotification(const QString &title, const QString &message, const int &displayTime, const QString ¬ificationType); - int pressButton(const QString &button); + int navigate(const QString &to); int systemCommand(const QString &command); void update();