From 72096bf1c8b940981f87964a397108dcb2ff3689 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sat, 19 Feb 2011 18:24:11 +0000 Subject: [PATCH] Move ArtLoader from mpris_common.h to its own file, add libxrme to 3rdparty, add a working XMPP remote. --- 3rdparty/libxrme/CMakeLists.txt | 51 ++ 3rdparty/libxrme/common.cpp | 43 ++ 3rdparty/libxrme/common.h | 65 +++ 3rdparty/libxrme/connection.cpp | 498 +++++++++++++++++++ 3rdparty/libxrme/connection.h | 152 ++++++ 3rdparty/libxrme/handler.cpp | 39 ++ 3rdparty/libxrme/handler.h | 43 ++ 3rdparty/libxrme/mediaplayerhandler.cpp | 149 ++++++ 3rdparty/libxrme/mediaplayerhandler.h | 52 ++ 3rdparty/libxrme/mediaplayerinterface.cpp | 60 +++ 3rdparty/libxrme/mediaplayerinterface.h | 67 +++ 3rdparty/libxrme/remotecontrolhandler.cpp | 145 ++++++ 3rdparty/libxrme/remotecontrolhandler.h | 63 +++ 3rdparty/libxrme/remotecontrolinterface.cpp | 72 +++ 3rdparty/libxrme/remotecontrolinterface.h | 66 +++ CMakeLists.txt | 2 +- src/CMakeLists.txt | 67 +-- src/core/{mpris_common.cpp => artloader.cpp} | 16 +- src/core/artloader.h | 62 +++ src/core/mpris.cpp | 1 + src/core/mpris.h | 2 +- src/core/mpris1.cpp | 1 + src/core/mpris1.h | 4 +- src/core/mpris2.cpp | 3 +- src/core/mpris2.h | 2 +- src/core/mpris_common.h | 40 -- src/main.cpp | 20 +- src/remote/avahi.cpp | 95 ---- src/remote/avahi.h | 39 -- src/remote/bonjour.h | 18 - src/remote/bonjour.mm | 84 ---- src/remote/remote.cpp | 165 ++++++ src/remote/remote.h | 73 +++ src/remote/remoteconfig.cpp | 38 +- src/remote/remoteconfig.h | 4 +- src/remote/remoteconfig.ui | 76 ++- src/remote/xmpp.cpp | 93 ---- src/remote/xmpp.h | 47 -- src/remote/zeroconf.cpp | 25 - src/remote/zeroconf.h | 21 - src/ui/mainwindow.cpp | 18 + src/ui/mainwindow.h | 4 + 42 files changed, 2005 insertions(+), 580 deletions(-) create mode 100644 3rdparty/libxrme/CMakeLists.txt create mode 100644 3rdparty/libxrme/common.cpp create mode 100644 3rdparty/libxrme/common.h create mode 100644 3rdparty/libxrme/connection.cpp create mode 100644 3rdparty/libxrme/connection.h create mode 100644 3rdparty/libxrme/handler.cpp create mode 100644 3rdparty/libxrme/handler.h create mode 100644 3rdparty/libxrme/mediaplayerhandler.cpp create mode 100644 3rdparty/libxrme/mediaplayerhandler.h create mode 100644 3rdparty/libxrme/mediaplayerinterface.cpp create mode 100644 3rdparty/libxrme/mediaplayerinterface.h create mode 100644 3rdparty/libxrme/remotecontrolhandler.cpp create mode 100644 3rdparty/libxrme/remotecontrolhandler.h create mode 100644 3rdparty/libxrme/remotecontrolinterface.cpp create mode 100644 3rdparty/libxrme/remotecontrolinterface.h rename src/core/{mpris_common.cpp => artloader.cpp} (89%) create mode 100644 src/core/artloader.h delete mode 100644 src/remote/avahi.cpp delete mode 100644 src/remote/avahi.h delete mode 100644 src/remote/bonjour.h delete mode 100644 src/remote/bonjour.mm create mode 100644 src/remote/remote.cpp create mode 100644 src/remote/remote.h delete mode 100644 src/remote/xmpp.cpp delete mode 100644 src/remote/xmpp.h delete mode 100644 src/remote/zeroconf.cpp delete mode 100644 src/remote/zeroconf.h diff --git a/3rdparty/libxrme/CMakeLists.txt b/3rdparty/libxrme/CMakeLists.txt new file mode 100644 index 000000000..da6db254c --- /dev/null +++ b/3rdparty/libxrme/CMakeLists.txt @@ -0,0 +1,51 @@ +include_directories(${CMAKE_BINARY_DIR}) +include_directories(${GLOOX_INCLUDE_DIRS}) + +link_directories(${GLOOX_LIBRARY_DIRS}) + +set(SOURCES + common.cpp + connection.cpp + handler.cpp + mediaplayerhandler.cpp + mediaplayerinterface.cpp + remotecontrolhandler.cpp + remotecontrolinterface.cpp +) + +set(HEADERS + connection.h +) + +SET(PUBLIC_HEADERS + connection.h + common.h + mediaplayerinterface.h + remotecontrolinterface.h +) + +qt4_wrap_cpp(MOC ${HEADERS}) + +add_library(xrme ${SOURCES} ${MOC}) +target_link_libraries(xrme + ${QT_LIBRARIES} + ${GLOOX_LIBRARIES} +) + +# Install library +install(TARGETS xrme + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +# Install public headers +install(FILES ${PUBLIC_HEADERS} + DESTINATION include/xrme/ +) + +# Also install public headers into the cmake binary dir so packages can use +# libxrme in their source tree by doing include_directories(${CMAKE_BINARY_DIR}) +foreach(header ${PUBLIC_HEADERS}) + configure_file(${header} ${CMAKE_BINARY_DIR}/xrme/${header} COPYONLY) +endforeach(header) diff --git a/3rdparty/libxrme/common.cpp b/3rdparty/libxrme/common.cpp new file mode 100644 index 000000000..c43290476 --- /dev/null +++ b/3rdparty/libxrme/common.cpp @@ -0,0 +1,43 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "common.h" + +namespace xrme { + +const char* kXmlnsXrme = "http://purplehatstands.com/xmlns/xrme"; +const char* kXmlnsXrmeMediaPlayer = "http://purplehatstands.com/xmlns/xrme/mediaplayer"; +const char* kXmlnsXrmeRemoteControl = "http://purplehatstands.com/xmlns/xrme/remotecontrol"; + +Metadata::Metadata() + : track(0), + disc(0), + year(0), + length_millisec(0), + rating(0.0) { +} + +State::State() + : playback_state(PlaybackState_Stopped), + position_millisec(0), + volume(0.0), + can_go_next(false), + can_go_previous(false), + can_seek(false) { +} + +} // namespace xrme diff --git a/3rdparty/libxrme/common.h b/3rdparty/libxrme/common.h new file mode 100644 index 000000000..f35b784e3 --- /dev/null +++ b/3rdparty/libxrme/common.h @@ -0,0 +1,65 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef XRME_COMMON_H +#define XRME_COMMON_H + +#include + +namespace xrme { + +extern const char* kXmlnsXrme; +extern const char* kXmlnsXrmeMediaPlayer; +extern const char* kXmlnsXrmeRemoteControl; + +struct Metadata { + Metadata(); + + QString title; + QString artist; + QString album; + QString albumartist; + QString composer; + QString genre; + int track; + int disc; + int year; + int length_millisec; + double rating; // range 0.0 - 1.0 +}; + +struct State { + State(); + + enum PlaybackState { + PlaybackState_Stopped = 0, + PlaybackState_Playing = 1, + PlaybackState_Paused = 2, + }; + + PlaybackState playback_state; + int position_millisec; + double volume; // range 0.0 - 1.0 + bool can_go_next; + bool can_go_previous; + bool can_seek; + Metadata metadata; +}; + +} // namespace xrme + +#endif // XRME_COMMON_H diff --git a/3rdparty/libxrme/connection.cpp b/3rdparty/libxrme/connection.cpp new file mode 100644 index 000000000..dbd633762 --- /dev/null +++ b/3rdparty/libxrme/connection.cpp @@ -0,0 +1,498 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "common.h" +#include "connection.h" +#include "mediaplayerhandler.h" +#include "remotecontrolhandler.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrme { + +struct Connection::Private : public gloox::ConnectionListener, + public gloox::LogHandler, + public gloox::RosterListener, + public gloox::DiscoHandler { + Private(Connection* parent) + : parent_(parent), + server_(kDefaultServer), + jid_resource_(kDefaultJIDResource), + jid_host_(kDefaultJIDHost), + verbose_(false), + media_player_(NULL), + remote_control_(NULL), + spontaneous_disconnect_(true) {} + + static const char* kDefaultServer; + static const char* kDefaultJIDResource; + static const char* kDefaultJIDHost; + + Connection* parent_; + + // Stuff the user sets before Connect() + QString username_; + QString password_; + QString agent_name_; + QString server_; + QString jid_resource_; + QString jid_host_; + bool verbose_; + + // Interfaces + MediaPlayerInterface* media_player_; + RemoteControlInterface* remote_control_; + QList handlers_; + + // Stuff that is valid when we're connected. + QScopedPointer client_; + QScopedPointer socket_notifier_; + + // After discovering a peer we query it to find its capabilities. Only after + // it replies to the query do we put it in peers_ and emit PeerFound(). + QList peers_; + QList querying_peers_; + + bool has_peer(const QString& jid_resource) const; + + // We can't destroy the client_ in the onDisconnect() handler, so we have to + // do it with a QTimer if we get a spontaneous disconnect. + bool spontaneous_disconnect_; + + // gloox::MessageHandler + void handleMessage(gloox::Stanza* stanza, gloox::MessageSession* session = 0); + + // gloox::ConnectionListener + void onConnect(); + void onDisconnect(gloox::ConnectionError e); + bool onTLSConnect(const gloox::CertInfo& info); + + // gloox::LogHandler + void handleLog(gloox::LogLevel level, gloox::LogArea area, + const std::string& message); + + // gloox::IqHandler + bool handleIq(gloox::Stanza* stanza); + bool handleIqID(gloox::Stanza* stanza, int context); + + // gloox::RosterListener + void handleItemAdded(const gloox::JID&) {} + void handleItemSubscribed(const gloox::JID&) {} + void handleItemRemoved(const gloox::JID&) {} + void handleItemUpdated(const gloox::JID&) {} + void handleItemUnsubscribed(const gloox::JID&) {} + void handleRoster(const gloox::Roster&) {} + void handleRosterPresence(const gloox::RosterItem& item, const std::string& resource, gloox::Presence presence, const std::string& msg); + void handleSelfPresence(const gloox::RosterItem&, const std::string&, gloox::Presence, const std::string&) {} + bool handleSubscriptionRequest(const gloox::JID&, const std::string&) { return false; } + bool handleUnsubscriptionRequest(const gloox::JID&, const std::string&) { return false; } + void handleNonrosterPresence(gloox::Stanza*) {} + void handleRosterError(gloox::Stanza*) {} + + // gloox::DiscoHandler + void handleDiscoInfoResult(gloox::Stanza* stanza, int context); + void handleDiscoItemsResult(gloox::Stanza*, int) {} + void handleDiscoError(gloox::Stanza* stanza, int context); +}; + +const char* Connection::Private::kDefaultServer = "talk.google.com"; +const char* Connection::Private::kDefaultJIDResource = "xrmeagent"; +const char* Connection::Private::kDefaultJIDHost = "gmail.com"; + + +Connection::Connection(QObject* parent) + : QObject(parent), + d(new Private(this)) { +} + +Connection::~Connection() { + qDeleteAll(d->handlers_); +} + +void Connection::set_username(const QString& username) { d->username_ = username; } +void Connection::set_password(const QString& password) { d->password_ = password; } +void Connection::set_agent_name(const QString& agent_name) { d->agent_name_ = agent_name; } +void Connection::set_server(const QString& server) { d->server_ = server; } +void Connection::set_jid_resource(const QString& resource) { d->jid_resource_ = resource; } +void Connection::set_jid_host(const QString& host) { d->jid_host_ = host; } +void Connection::set_verbose(bool verbose) { d->verbose_ = verbose; } + +QString Connection::username() const { return d->username_; } +QString Connection::password() const { return d->password_; } +QString Connection::agent_name() const { return d->agent_name_; } +QString Connection::server() const { return d->server_; } +QString Connection::jid_resource() const { return d->jid_resource_; } +QString Connection::jid_host() const { return d->jid_host_; } +bool Connection::is_verbose() const { return d->verbose_; } + +void Connection::SetMediaPlayer(MediaPlayerInterface* interface) { + if (d->media_player_) { + qWarning() << "Connection::SetMediaPlayer: this connection already has a" + " MediaPlayerInterface set"; + return; + } + + if (!interface) { + qWarning() << "Connection::SetMediaPlayer called with NULL interface"; + return; + } + + d->media_player_ = interface; + d->handlers_ << new MediaPlayerHandler(interface); +} + +void Connection::SetRemoteControl(RemoteControlInterface* interface) { + if (d->media_player_) { + qWarning() << "Connection::RemoteControlInterface: this connection already" + " has a RemoteControlInterface set"; + return; + } + + if (!interface) { + qWarning() << "Connection::SetRemoteControl called with NULL interface"; + return; + } + + d->remote_control_ = interface; + d->handlers_ << new RemoteControlHandler(interface); +} + +bool Connection::is_connected() const { + return (d->client_ && d->client_->state() == gloox::StateConnected); +} + +QString Connection::jid() const { + if (is_connected()) { + return QString::fromUtf8(d->client_->jid().full().c_str()); + } + return QString(); +} + +Connection::PeerList Connection::peers() const { + if (is_connected()) { + return d->peers_; + } + return PeerList(); +} + +Connection::PeerList Connection::peers(Peer::Capability cap) const { + PeerList ret; + foreach (const Peer& peer, peers()) { + if (peer.caps_ & cap) { + ret << peer; + } + } + return ret; +} + +bool Connection::Connect() { + if (d->username_.isEmpty() || d->password_.isEmpty() || d->agent_name_.isEmpty()) { + qWarning() << __PRETTY_FUNCTION__ + << ": A required field (username/password/agent_name) was empty"; + return false; + } + + // Construct the JID - append the default host if the user didn't provide one + QString jid = d->username_; + if (!jid.contains('@')) { + jid.append("@" + d->jid_host_); + } + jid.append("/" + d->jid_resource_); + + // Create a new connection + d->client_.reset(new gloox::Client(gloox::JID(jid.toUtf8().constData()), + d->password_.toUtf8().constData())); + gloox::ConnectionTCPClient* connection = new gloox::ConnectionTCPClient( + d->client_.data(), d->client_->logInstance(), + d->server_.toUtf8().constData()); + d->client_->setConnectionImpl(connection); + + // Add listeners + d->client_->registerConnectionListener(d.data()); + d->client_->rosterManager()->registerRosterListener(d.data()); + d->client_->logInstance().registerLogHandler( + gloox::LogLevelDebug, gloox::LogAreaAll, d.data()); + + // Setup disco + d->client_->disco()->setIdentity("client", "bot"); + d->client_->disco()->setVersion(d->agent_name_.toUtf8().constData(), std::string()); + d->client_->disco()->addFeature(kXmlnsXrme); + + // Initialise the handlers + foreach (Handler* handler, d->handlers_) { + handler->Init(this, d->client_.data()); + } + + // Set presence + d->client_->setPresence(gloox::PresenceAvailable, -128); + + // Connect + if (!d->client_->connect(false)) { + d->client_.reset(); + foreach (Handler* handler, d->handlers_) { + handler->Reset(); + } + return false; + } + + // Listen on the connection's socket + d->socket_notifier_.reset(new QSocketNotifier( + connection->socket(), QSocketNotifier::Read)); + connect(d->socket_notifier_.data(), SIGNAL(activated(int)), + SLOT(SocketReadyReceive())); + + return true; +} + +void Connection::Disconnect() { + if (is_connected()) { + d->spontaneous_disconnect_ = false; + d->client_->disconnect(); + d->client_.reset(); + d->spontaneous_disconnect_ = true; + } +} + +void Connection::SocketReadyReceive() { + d->client_->recv(); +} + +void Connection::Private::onConnect() { + parent_->RefreshPeers(); + emit parent_->Connected(); +} + +void Connection::Private::onDisconnect(gloox::ConnectionError e) { + QString error_text; + switch (e) { + case gloox::ConnNoError: + case gloox::ConnUserDisconnected: + break; + case gloox::ConnStreamError: + error_text = QString::fromUtf8(client_->streamErrorText().c_str()); + break; + case gloox::ConnAuthenticationFailed: + error_text = QString("Authentication error (%1)").arg(client_->authError()); + break; + default: + error_text = QString("Unknown error (%1)").arg(e); + break; + } + + foreach (Handler* handler, handlers_) { + handler->Reset(); + } + + socket_notifier_->setEnabled(false); + socket_notifier_.reset(); + peers_.clear(); + querying_peers_.clear(); + + emit parent_->Disconnected(error_text); + + if (spontaneous_disconnect_) { + QTimer::singleShot(0, parent_, SLOT(CleanupClient())); + } +} + +void Connection::CleanupClient() { + d->client_.reset(); +} + +bool Connection::Private::onTLSConnect(const gloox::CertInfo& info) { + return true; +} + +void Connection::Private::handleLog(gloox::LogLevel level, gloox::LogArea area, + const std::string& message) { + if (!verbose_) { + return; + } + + QString prefix = "---"; + if (area == gloox::LogAreaXmlIncoming) { + prefix = "<<<"; + } else if (area == gloox::LogAreaXmlOutgoing) { + prefix = ">>>"; + } + + qDebug() << "XMPP" << prefix.toAscii().constData() << message.c_str(); +} + +void Connection::RefreshPeers() { + // Clear the lists + d->peers_.clear(); + d->querying_peers_.clear(); + + // Query presence + qDebug() << "Sending presence query"; + d->client_->send(gloox::Stanza::createPresenceStanza(d->client_->jid().bareJID())); +} + +Connection::Peer::Peer() + : caps_(0) { +} + +void Connection::Private::handleRosterPresence( + const gloox::RosterItem& item, const std::string& res, + gloox::Presence presence, const std::string&) { + // Ignore presence from anyone else + if (item.jid() != client_->jid().bare()) { + return; + } + + QString resource = QString::fromUtf8(res.c_str()); + + switch (presence) { + case gloox::PresenceUnknown: + case gloox::PresenceUnavailable: + // The peer went offline - did we know about him? + qDebug() << "Peer unavailable" << resource; + + for (int i=0 ; iPeerRemoved(peers_.takeAt(i)); + break; + } + } + break; + + default: + // The peer came online + if (!has_peer(resource)) { + qDebug() << "Got presence from" << resource; + + // This is a peer on our own bare JID, and we haven't seen it before + gloox::JID full_jid(item.jid()); + full_jid.setResource(res); + + Peer peer; + peer.jid_resource_ = resource; + querying_peers_ << peer; + + client_->disco()->getDiscoInfo(full_jid, std::string(), this, 0); + } + } +} + +bool Connection::Private::has_peer(const QString& jid_resource) const { + foreach (const Peer& peer, peers_) { + if (peer.jid_resource_ == jid_resource) { + return true; + } + } + + foreach (const Peer& peer, querying_peers_) { + if (peer.jid_resource_ == jid_resource) { + return true; + } + } +} + +void Connection::Private::handleDiscoInfoResult(gloox::Stanza* stanza, int context) { + // Is this from our own bare JID? + if (stanza->from().bareJID() != client_->jid().bareJID()) { + return; + } + + QString resource = QString::fromUtf8(stanza->from().resource().c_str()); + + qDebug() << "Got disco info from" << resource; + + // Are we currently querying this peer? + int querying_peer_index = -1; + for (int i=0 ; ifindChild("query"); + if (!query) { + return; + } + + gloox::Tag* identity = query->findChild("identity"); + gloox::Tag::TagList features = query->findChildren("feature"); + if (identity == NULL || features.size() == 0) { + return; + } + + // Fill in the name. + peer.agent_name_ = QString::fromUtf8(identity->findAttribute("name").c_str()); + + // Fill in the list of capabilities. + foreach (gloox::Tag* feature, features) { + const std::string feature_name = feature->findAttribute("var"); + if (feature_name == kXmlnsXrmeMediaPlayer) { + peer.caps_ |= Peer::MediaPlayer; + } + if (feature_name == kXmlnsXrmeRemoteControl) { + peer.caps_ |= Peer::RemoteControl; + } + } + + // No recognised capabilities? Discard the peer. + if (peer.caps_ == Peer::None) { + return; + } + + peers_ << peer; + emit parent_->PeerFound(peer); + + qDebug() << "Peer found:" << peer.agent_name_ << peer.jid_resource_ << peer.caps_; +} + +void Connection::Private::handleDiscoError(gloox::Stanza* stanza, int context) { + QString resource = QString::fromUtf8(stanza->from().resource().c_str()); + + // Remove this peer if we're currently querying it + for (int i=0 ; i + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef LIBXRME_CONNECTION_H +#define LIBXRME_CONNECTION_H + +#include +#include +#include +#include + +namespace xrme { + +class MediaPlayerInterface; +class RemoteControlInterface; + +class Connection : public QObject { + Q_OBJECT + +public: + Connection(QObject* parent = NULL); + ~Connection(); + + struct Peer { + Peer(); + + enum Capability { + None = 0x00, + + MediaPlayer = 0x01, + RemoteControl = 0x02, + MediaStorage = 0x04, + }; + Q_DECLARE_FLAGS(Capabilities, Capability); + + QString agent_name_; + QString jid_resource_; + Capabilities caps_; + }; + typedef QList PeerList; + + // The username and password MUST be set before calling Connect(). + void set_username(const QString& username); + void set_password(const QString& password); + + // The agent name MUST be set before calling Connect(). The agent name is a + // friendly user-visible name assigned to this agent to help the user pick it + // out amongst other media players or remotes. It might contain the name of + // the application or the computer's hostname. + void set_agent_name(const QString& agent_name); + + // Sets the hostname of the XMPP server to connect to. Defaults to + // "talk.google.com". + void set_server(const QString& server); + + // Sets the resource string to use in the JID. Defaults to "xrmeagent". + void set_jid_resource(const QString& resource); + + // Sets the host part to append to the JID if the user didn't specify one in + // the username. The host part is the part of the JID after the @ and before + // the /, eg: "username@host/resource". Defaults to "gmail.com". + void set_jid_host(const QString& host); + + // If this is set then detailed XMPP output will be printed. Defaults to off. + void set_verbose(bool verbose); + + // The following getters just return the data set above. + QString username() const; + QString password() const; + QString agent_name() const; + QString server() const; + QString jid_resource() const; + QString jid_host() const; + bool is_verbose() const; + + // Sets a media player on the connection. Calling this means the XRME agent + // will advertise itself as a media player, and will be able to controlled by + // remote control agents. Should be called before calling Connect(). The + // Connection will NOT take ownership of the MediaPlayerInterface, and the + // MediaPlayerInterface MUST stay alive for as long as the connection. + void SetMediaPlayer(MediaPlayerInterface* interface); + + // Sets a remote control on the connection. Calling this means the XRME agent + // will advertise itself as a remote control, and will receive state changes + // from media player agents. Should be called before calling Connect(). The + // Connection will NOT take ownership of the RemoteControlInterface, and the + // RemoteControlInterface MUST stay alive for as long as the connection. + void SetRemoteControl(RemoteControlInterface* interface); + + // Returns true after Connected() is emitted. + bool is_connected() const; + + // Returns the user's actual JID. This is only valid if is_connected() is + // true. Before the connection is complete it will return a null QString. + QString jid() const; + + // Returns the list of all known peers. You can refresh this list by calling + // RefreshPeers(). PeerFound() is emitted any time a peer is added to this + // list. + PeerList peers() const; + + // Returns the list of known peers that support a certain capability. + PeerList peers(Peer::Capability cap) const; + +public slots: + // Starts connecting and returns immediately. Will emit Connected() or + // Disconnected() later. The username and password must already be set. + // Returns true on success, false if there was some problem starting the + // connection (eg. invalid/unspecified username/password/agent_name). + bool Connect(); + + // Disconnects immediately and emits Disconnected(). + void Disconnect(); + + // Clears the internal list of peers and sends XMPP queries to discover the + // current list. Emits PeerFound(). + void RefreshPeers(); + +signals: + void Connected(); + void Disconnected(const QString& error); + + void PeerFound(const xrme::Connection::Peer& peer); + void PeerRemoved(const xrme::Connection::Peer& peer); + +private slots: + void SocketReadyReceive(); + void CleanupClient(); + +private: + struct Private; + friend struct Private; + QScopedPointer d; +}; + +} // namespace xrme + +#endif diff --git a/3rdparty/libxrme/handler.cpp b/3rdparty/libxrme/handler.cpp new file mode 100644 index 000000000..dd98dd89e --- /dev/null +++ b/3rdparty/libxrme/handler.cpp @@ -0,0 +1,39 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "handler.h" + +#include + +namespace xrme { + +Handler::Handler() + : client_(NULL), + connection_(NULL) { +} + +void Handler::Init(Connection* connection, gloox::Client* client) { + connection_ = connection; + client_ = client; +} + +void Handler::Reset() { + connection_ = NULL; + client_ = NULL; +} + +} // namespace xrme diff --git a/3rdparty/libxrme/handler.h b/3rdparty/libxrme/handler.h new file mode 100644 index 000000000..a1811c3f8 --- /dev/null +++ b/3rdparty/libxrme/handler.h @@ -0,0 +1,43 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef LIBXRME_HANDLER_H +#define LIBXRME_HANDLER_H + +namespace gloox { + class Client; +} + +namespace xrme { +class Connection; + +class Handler { +public: + Handler(); + virtual ~Handler() {} + + virtual void Init(Connection* connection, gloox::Client* client); + virtual void Reset(); + +protected: + Connection* connection_; + gloox::Client* client_; +}; + +} // namespace xrme + +#endif // LIBXRME_HANDLER_H diff --git a/3rdparty/libxrme/mediaplayerhandler.cpp b/3rdparty/libxrme/mediaplayerhandler.cpp new file mode 100644 index 000000000..b84b914e3 --- /dev/null +++ b/3rdparty/libxrme/mediaplayerhandler.cpp @@ -0,0 +1,149 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "connection.h" +#include "mediaplayerhandler.h" +#include "mediaplayerinterface.h" +#include "remotecontrolhandler.h" + +#include +#include + +#include +#include + +namespace xrme { + +MediaPlayerHandler::MediaPlayerHandler(MediaPlayerInterface* interface) + : interface_(interface) { + interface_->Attach(this); +} + +void MediaPlayerHandler::StateChanged() { + if (!connection_) { + return; + } + + State s = interface_->state(); + + foreach (const Connection::Peer& peer, connection_->peers(Connection::Peer::RemoteControl)) { + gloox::JID to(client_->jid().bareJID()); + to.setResource(peer.jid_resource_.toUtf8().constData()); + + gloox::Tag* stanza = gloox::Stanza::createIqStanza( + to, std::string(), gloox::StanzaIqSet, kXmlnsXrmeRemoteControl); + gloox::Tag* state = new gloox::Tag(stanza, "state"); + state->addAttribute("xmlns", kXmlnsXrmeRemoteControl); + state->addAttribute("playback_state", s.playback_state); + state->addAttribute("position_millisec", s.position_millisec); + state->addAttribute("volume", QString::number(s.volume, 'f').toUtf8().constData()); + state->addAttribute("can_go_next", s.can_go_next ? 1 : 0); + state->addAttribute("can_go_previous", s.can_go_previous ? 1 : 0); + state->addAttribute("can_seek", s.can_seek ? 1 : 0); + + gloox::Tag* metadata = new gloox::Tag(state, "metadata"); + metadata->addAttribute("title", s.metadata.title.toUtf8().constData()); + metadata->addAttribute("artist", s.metadata.artist.toUtf8().constData()); + metadata->addAttribute("album", s.metadata.album.toUtf8().constData()); + metadata->addAttribute("albumartist", s.metadata.albumartist.toUtf8().constData()); + metadata->addAttribute("composer", s.metadata.composer.toUtf8().constData()); + metadata->addAttribute("genre", s.metadata.genre.toUtf8().constData()); + metadata->addAttribute("track", s.metadata.track); + metadata->addAttribute("disc", s.metadata.disc); + metadata->addAttribute("year", s.metadata.year); + metadata->addAttribute("length_millisec", s.metadata.length_millisec); + metadata->addAttribute("rating", QString::number(s.metadata.rating, 'f').toUtf8().constData()); + + client_->send(stanza); + } +} + +void MediaPlayerHandler::AlbumArtChanged() { + if (!connection_) { + return; + } + + QImage image = interface_->album_art(); + + // Scale the image down if it's too big + if (!image.isNull() && (image.width() > kMaxAlbumArtSize || + image.height() > kMaxAlbumArtSize)) { + image = image.scaled(kMaxAlbumArtSize,kMaxAlbumArtSize, + Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + // Write the image data + QByteArray image_data; + if (!image.isNull()) { + QBuffer buffer(&image_data); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "JPEG"); + } + + // Convert to base64 + QByteArray image_data_base64 = image_data.toBase64(); + + // Send the IQs + foreach (const Connection::Peer& peer, connection_->peers(Connection::Peer::RemoteControl)) { + gloox::JID to(client_->jid().bareJID()); + to.setResource(peer.jid_resource_.toUtf8().constData()); + + gloox::Tag* stanza = gloox::Stanza::createIqStanza( + to, std::string(), gloox::StanzaIqSet, kXmlnsXrmeRemoteControl); + gloox::Tag* album_art = new gloox::Tag(stanza, "album_art"); + album_art->addAttribute("xmlns", kXmlnsXrmeRemoteControl); + album_art->setCData(image_data_base64.constData()); + + client_->send(stanza); + } +} + +void MediaPlayerHandler::Init(Connection* connection, gloox::Client* client) { + Handler::Init(connection, client); + + client->registerIqHandler(this, kXmlnsXrmeMediaPlayer); + client->disco()->addFeature(kXmlnsXrmeMediaPlayer); +} + +bool MediaPlayerHandler::handleIq(gloox::Stanza* stanza) { + // Ignore stanzas from anyone else + if (stanza->from().bareJID() != client_->jid().bareJID()) { + return false; + } + + if (stanza->hasChild("playpause")) { + interface_->PlayPause(); + } else if (stanza->hasChild("stop")) { + interface_->Stop(); + } else if (stanza->hasChild("previous")) { + interface_->Previous(); + } else if (stanza->hasChild("next")) { + interface_->Next(); + } else if (stanza->hasChild("querystate")) { + StateChanged(); + AlbumArtChanged(); + } else { + qWarning() << "Unknown command received from" + << stanza->from().resource().c_str() + << stanza->xml().c_str(); + return false; + } + + return true; +} + +} // namespace xrme diff --git a/3rdparty/libxrme/mediaplayerhandler.h b/3rdparty/libxrme/mediaplayerhandler.h new file mode 100644 index 000000000..559f9d945 --- /dev/null +++ b/3rdparty/libxrme/mediaplayerhandler.h @@ -0,0 +1,52 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef LIBXRME_MEDIAPLAYERHANDLER_H +#define LIBXRME_MEDIAPLAYERHANDLER_H + +#include "handler.h" + +#include + +namespace xrme { + +class MediaPlayerInterface; + +class MediaPlayerHandler : public Handler, + public gloox::IqHandler { +public: + MediaPlayerHandler(MediaPlayerInterface* interface); + + static const int kMaxAlbumArtSize = 300; + + void StateChanged(); + void AlbumArtChanged(); + + // Handler + void Init(Connection* connection, gloox::Client* client); + + // gloox::IqHandler + bool handleIq(gloox::Stanza* stanza); + bool handleIqID(gloox::Stanza*, int) {} + +private: + MediaPlayerInterface* interface_; +}; + +} // namespace xrme + +#endif // LIBXRME_MEDIAPLAYERHANDLER_H diff --git a/3rdparty/libxrme/mediaplayerinterface.cpp b/3rdparty/libxrme/mediaplayerinterface.cpp new file mode 100644 index 000000000..3abc1f488 --- /dev/null +++ b/3rdparty/libxrme/mediaplayerinterface.cpp @@ -0,0 +1,60 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "mediaplayerhandler.h" +#include "mediaplayerinterface.h" + +#include + +namespace xrme { + +struct MediaPlayerInterface::Private { + Private() + : handler_(NULL) {} + + MediaPlayerHandler* handler_; +}; + + +MediaPlayerInterface::MediaPlayerInterface() + : d(new Private) { +} + +MediaPlayerInterface::~MediaPlayerInterface() { +} + +void MediaPlayerInterface::StateChanged() { + if (!d->handler_) { + return; + } + + d->handler_->StateChanged(); +} + +void MediaPlayerInterface::AlbumArtChanged() { + if (!d->handler_) { + return; + } + + d->handler_->AlbumArtChanged(); +} + +void MediaPlayerInterface::Attach(MediaPlayerHandler* handler) { + d->handler_ = handler; +} + +} // namespace xrme diff --git a/3rdparty/libxrme/mediaplayerinterface.h b/3rdparty/libxrme/mediaplayerinterface.h new file mode 100644 index 000000000..424c4bff2 --- /dev/null +++ b/3rdparty/libxrme/mediaplayerinterface.h @@ -0,0 +1,67 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef LIBXRME_MEDIAPLAYERINTERFACE_H +#define LIBXRME_MEDIAPLAYERINTERFACE_H + +#include + +#include +#include + +namespace xrme { + +class MediaPlayerHandler; + +class MediaPlayerInterface { +public: + MediaPlayerInterface(); + virtual ~MediaPlayerInterface(); + + // Control playback + virtual void PlayPause() = 0; + virtual void Stop() = 0; + virtual void Next() = 0; + virtual void Previous() = 0; + + // Query the current state of the player. StateChanged() should be called + // when any part of the state changes. + virtual State state() const = 0; + + // Query the album art of the currently playing song. This is separate from + // state() because it is bigger and needs to be sent less often. + // AlbumArtChanged() should be called when this changes. Return a null + // QImage() if there is no song playing or the song has no album art. + virtual QImage album_art() const = 0; + + // Call when the values returned from the above getters have changed. + virtual void StateChanged(); + virtual void AlbumArtChanged(); + +private: + Q_DISABLE_COPY(MediaPlayerInterface); + friend class MediaPlayerHandler; + + void Attach(MediaPlayerHandler* handler); + + struct Private; + QScopedPointer d; +}; + +} // namespace xrme + +#endif // LIBXRME_MEDIAPLAYERINTERFACE_H diff --git a/3rdparty/libxrme/remotecontrolhandler.cpp b/3rdparty/libxrme/remotecontrolhandler.cpp new file mode 100644 index 000000000..947f43be0 --- /dev/null +++ b/3rdparty/libxrme/remotecontrolhandler.cpp @@ -0,0 +1,145 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "mediaplayerhandler.h" +#include "remotecontrolinterface.h" +#include "remotecontrolhandler.h" + +#include +#include + +#include +#include + +namespace xrme { + +RemoteControlHandler::RemoteControlHandler(RemoteControlInterface* interface) + : interface_(interface) { + interface_->Attach(this); +} + +void RemoteControlHandler::Init(Connection* connection, gloox::Client* client) { + Handler::Init(connection, client); + + client->registerIqHandler(this, kXmlnsXrmeRemoteControl); + client->disco()->addFeature(kXmlnsXrmeRemoteControl); +} + +void RemoteControlHandler::SendIQ(const QString& jid_resource, + gloox::StanzaSubType type, + const QString& command) { + if (!client_) { + return; + } + + gloox::JID to(client_->jid()); + to.setResource(jid_resource.toUtf8().constData()); + + gloox::Tag* stanza = gloox::Stanza::createIqStanza( + to, client_->getID(), type, kXmlnsXrmeMediaPlayer); + gloox::Tag* c = new gloox::Tag(stanza, command.toUtf8().constData()); + c->addAttribute("xmlns", kXmlnsXrmeMediaPlayer); + + client_->send(stanza); +} + +void RemoteControlHandler::PlayPause(const QString& jid_resource) { + SendIQ(jid_resource, gloox::StanzaIqSet, "playpause"); +} + +void RemoteControlHandler::Stop(const QString& jid_resource) { + SendIQ(jid_resource, gloox::StanzaIqSet, "stop"); +} + +void RemoteControlHandler::Next(const QString& jid_resource) { + SendIQ(jid_resource, gloox::StanzaIqSet, "next"); +} + +void RemoteControlHandler::Previous(const QString& jid_resource) { + SendIQ(jid_resource, gloox::StanzaIqSet, "previous"); +} + +void RemoteControlHandler::QueryState(const QString& jid_resource) { + SendIQ(jid_resource, gloox::StanzaIqGet, "querystate"); +} + +int RemoteControlHandler::ParseInt(gloox::Tag* tag, const char* attribute_name) { + return ParseString(tag, attribute_name).toInt(); +} + +double RemoteControlHandler::ParseDouble(gloox::Tag* tag, const char* attribute_name) { + return ParseString(tag, attribute_name).toFloat(); +} + +QString RemoteControlHandler::ParseString(gloox::Tag* tag, const char* attribute_name) { + return QString::fromUtf8(tag->findAttribute(attribute_name).c_str()); +} + +bool RemoteControlHandler::handleIq(gloox::Stanza* stanza) { + // Ignore stanzas from anyone else + if (stanza->from().bareJID() != client_->jid().bareJID()) { + return false; + } + + QString resource = QString::fromUtf8(stanza->from().resource().c_str()); + + qDebug() << resource << stanza->xml().c_str(); + + gloox::Tag* state = stanza->findChild("state"); + if (state) { + gloox::Tag* metadata = state->findChild("metadata"); + if (metadata) { + State s; + s.playback_state = State::PlaybackState(ParseInt(state, "playback_state")); + s.position_millisec = ParseInt(state, "position_millisec"); + s.volume = ParseDouble(state, "volume"); + s.can_go_next = ParseInt(state, "can_go_next"); + s.can_go_previous = ParseInt(state, "can_go_previous"); + s.can_seek = ParseInt(state, "can_seek"); + + s.metadata.title = ParseString(metadata, "title"); + s.metadata.artist = ParseString(metadata, "artist"); + s.metadata.album = ParseString(metadata, "album"); + s.metadata.albumartist = ParseString(metadata, "albumartist"); + s.metadata.composer = ParseString(metadata, "composer"); + s.metadata.genre = ParseString(metadata, "genre"); + s.metadata.track = ParseInt(metadata, "track"); + s.metadata.disc = ParseInt(metadata, "disc"); + s.metadata.year = ParseInt(metadata, "year"); + s.metadata.length_millisec = ParseInt(metadata, "length_millisec"); + s.metadata.rating = ParseDouble(metadata, "rating"); + + interface_->StateChanged(resource, s); + } + } + + gloox::Tag* album_art = stanza->findChild("album_art"); + if (album_art) { + QByteArray data(album_art->cdata().c_str(), album_art->cdata().size()); + + QImage image; + if (!data.isEmpty()) { + image.loadFromData(QByteArray::fromBase64(data), "JPEG"); + } + + interface_->AlbumArtChanged(resource, image); + } + + return state || album_art; +} + +} // namespace xrme diff --git a/3rdparty/libxrme/remotecontrolhandler.h b/3rdparty/libxrme/remotecontrolhandler.h new file mode 100644 index 000000000..3e3642de5 --- /dev/null +++ b/3rdparty/libxrme/remotecontrolhandler.h @@ -0,0 +1,63 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef REMOTECONTROLHANDLER_H +#define REMOTECONTROLHANDLER_H + +#include "handler.h" + +#include + +#include + +namespace xrme { + +class RemoteControlInterface; + +class RemoteControlHandler : public Handler, + public gloox::IqHandler { +public: + RemoteControlHandler(RemoteControlInterface* interface); + + void PlayPause(const QString& jid_resource); + void Stop(const QString& jid_resource); + void Next(const QString& jid_resource); + void Previous(const QString& jid_resource); + + void QueryState(const QString& jid_resource); + + // Handler + void Init(Connection* connection, gloox::Client* client); + + // gloox::IqHandler + bool handleIq(gloox::Stanza* stanza); + bool handleIqID(gloox::Stanza*, int) {} + +private: + void SendIQ(const QString& jid_resource, gloox::StanzaSubType type, + const QString& command); + static int ParseInt(gloox::Tag* tag, const char* attribute_name); + static double ParseDouble(gloox::Tag* tag, const char* attribute_name); + static QString ParseString(gloox::Tag* tag, const char* attribute_name); + +private: + RemoteControlInterface* interface_; +}; + +} // namespace xrme + +#endif // REMOTECONTROLHANDLER_H diff --git a/3rdparty/libxrme/remotecontrolinterface.cpp b/3rdparty/libxrme/remotecontrolinterface.cpp new file mode 100644 index 000000000..8dad99664 --- /dev/null +++ b/3rdparty/libxrme/remotecontrolinterface.cpp @@ -0,0 +1,72 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#include "remotecontrolhandler.h" +#include "remotecontrolinterface.h" + +namespace xrme { + +struct RemoteControlInterface::Private { + Private() + : handler_(NULL) {} + + RemoteControlHandler* handler_; +}; + + +RemoteControlInterface::RemoteControlInterface() + : d(new Private) { +} + +RemoteControlInterface::~RemoteControlInterface() { +} + +void RemoteControlInterface::PlayPause(const QString& peer_jid_resource) { + if (d->handler_) { + d->handler_->PlayPause(peer_jid_resource); + } +} + +void RemoteControlInterface::Stop(const QString& peer_jid_resource) { + if (d->handler_) { + d->handler_->Stop(peer_jid_resource); + } +} + +void RemoteControlInterface::Next(const QString& peer_jid_resource) { + if (d->handler_) { + d->handler_->Next(peer_jid_resource); + } +} + +void RemoteControlInterface::Previous(const QString& peer_jid_resource) { + if (d->handler_) { + d->handler_->Previous(peer_jid_resource); + } +} + +void RemoteControlInterface::QueryState(const QString& peer_jid_resource) { + if (d->handler_) { + d->handler_->QueryState(peer_jid_resource); + } +} + +void RemoteControlInterface::Attach(RemoteControlHandler* handler) { + d->handler_ = handler; +} + +} // namespace xrme diff --git a/3rdparty/libxrme/remotecontrolinterface.h b/3rdparty/libxrme/remotecontrolinterface.h new file mode 100644 index 000000000..25a1987d2 --- /dev/null +++ b/3rdparty/libxrme/remotecontrolinterface.h @@ -0,0 +1,66 @@ +/* This file is part of the XMPP Remote Media Extension. + Copyright 2010, David Sansome + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + 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 . +*/ + +#ifndef XRME_REMOTECONTROLINTERFACE_H +#define XRME_REMOTECONTROLINTERFACE_H + +#include "common.h" +#include "connection.h" + +class QImage; + +namespace xrme { + +class RemoteControlHandler; + +class RemoteControlInterface { +public: + RemoteControlInterface(); + virtual ~RemoteControlInterface(); + + // All functions here will work asynchronously and return immediately. + + // Call these to control the playback of a MediaPlayer. + void PlayPause(const QString& peer_jid_resource); + void Stop(const QString& peer_jid_resource); + void Next(const QString& peer_jid_resource); + void Previous(const QString& peer_jid_resource); + + // Call this to query the MediaPlayer. StateChanged() will be called later. + void QueryState(const QString& peer_jid_resource); + + // Called whenever the MediaPlayer's state changes. + virtual void StateChanged(const QString& peer_jid_resource, + const State& state) = 0; + + // Called whenever the MediaPlayer's album art changes. + virtual void AlbumArtChanged(const QString& peer_jid_resource, + const QImage& art) = 0; + +private: + Q_DISABLE_COPY(RemoteControlInterface); + friend class RemoteControlHandler; + + void Attach(RemoteControlHandler* handler); + + struct Private; + QScopedPointer d; +}; + +} // namespace xrme + +#endif // XRME_REMOTECONTROLINTERFACE_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ff72327..4237af93e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,7 +237,7 @@ option(STATIC_SQLITE "Compile and use a static sqlite3 library" ON) if(ENABLE_REMOTE AND GLOOX_LIBRARIES) set(HAVE_REMOTE ON) - add_subdirectory(3rdparty/keychain) + add_subdirectory(3rdparty/libxrme) endif(ENABLE_REMOTE AND GLOOX_LIBRARIES) set(HAVE_STATIC_SQLITE ${STATIC_SQLITE}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0bd6362f1..73eff8722 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ if(WIN32) include_directories(../3rdparty/qtwin) endif(WIN32) +include_directories(${CMAKE_BINARY_DIR}) include_directories(${GLIB_INCLUDE_DIRS}) include_directories(${LIBXML_INCLUDE_DIRS}) include_directories(${GOBJECT_INCLUDE_DIRS}) @@ -52,6 +53,7 @@ set(SOURCES analyzers/turbine.cpp core/albumcoverloader.cpp + core/artloader.cpp core/backgroundstreams.cpp core/backgroundthread.cpp core/commandlineoptions.cpp @@ -66,7 +68,6 @@ set(SOURCES core/gnomeglobalshortcutbackend.cpp core/kittenloader.cpp core/mergedproxymodel.cpp - core/mpris_common.cpp core/musicstorage.cpp core/network.cpp core/networkproxyfactory.cpp @@ -258,6 +259,7 @@ set(HEADERS analyzers/turbine.h core/albumcoverloader.h + core/artloader.h core/backgroundstreams.h core/backgroundthread.h core/database.h @@ -268,7 +270,6 @@ set(HEADERS core/kittenloader.h core/mergedproxymodel.h core/mimedata.h - core/mpris_common.h core/network.h core/organise.h core/player.h @@ -663,36 +664,6 @@ if(HAVE_DBUS) # Gnome Screensaver DBus interface list(APPEND SOURCES ui/dbusscreensaver.cpp) - - if(HAVE_REMOTE) - # TODO: Use CMake macro when it supports -i - find_program(QDBUSXML2CPP qdbusxml2cpp) - add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp - ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h - COMMAND ${QDBUSXML2CPP} - dbus/org.freedesktop.Avahi.EntryGroup.xml - -p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup - -i dbus/metatypes.h - DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h) - list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp) - - add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp - ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h - COMMAND ${QDBUSXML2CPP} - dbus/org.freedesktop.Avahi.Server.xml - -p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver - -i dbus/metatypes.h - DEPENDS dbus/org.freedesktop.Avahi.Server.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h) - list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp) - endif(HAVE_REMOTE) endif(HAVE_DBUS) # Libgpod device backend @@ -788,24 +759,15 @@ if(HAVE_SCRIPTING_PYTHON) endif(HAVE_SCRIPTING_PYTHON) if(HAVE_REMOTE) - include_directories(${GLOOX_INCLUDE_DIRS}) - link_directories(${GLOOX_LIBRARY_DIRS}) - include_directories(../3rdparty/keychain) list(APPEND UI remote/remoteconfig.ui) - list(APPEND HEADERS remote/remoteconfig.h) - list(APPEND SOURCES remote/remoteconfig.cpp) - list(APPEND HEADERS remote/xmpp.h) - list(APPEND SOURCES remote/xmpp.cpp) - list(APPEND SOURCES remote/zeroconf.cpp) - - if(APPLE) - list(APPEND SOURCES remote/bonjour.mm) - endif(APPLE) - - if(HAVE_DBUS) - list(APPEND SOURCES remote/avahi.cpp) - list(APPEND HEADERS remote/avahi.h) - endif(HAVE_DBUS) + list(APPEND HEADERS + remote/remote.h + remote/remoteconfig.h + ) + list(APPEND SOURCES + remote/remote.cpp + remote/remoteconfig.cpp + ) endif(HAVE_REMOTE) # OS-specific sources that should be searched for translatable strings even @@ -846,6 +808,11 @@ list(APPEND OTHER_SOURCES devices/wmdmlister.h devices/wmdmloader.h devices/wmdmloader.cpp + remote/remote.cpp + remote/remote.h + remote/remoteconfig.cpp + remote/remoteconfig.h + ${CMAKE_CURRENT_BINARY_DIR}/ui_remoteconfig.h ui/macsystemtrayicon.h ui/macsystemtrayicon.mm ui/wiimotedevshortcutsconfig.cpp @@ -968,7 +935,7 @@ endif(HAVE_SCRIPTING_PYTHON) if(HAVE_REMOTE) target_link_libraries(clementine_lib ${GLOOX_LIBRARIES}) - target_link_libraries(clementine_lib keychain) + target_link_libraries(clementine_lib xrme) endif(HAVE_REMOTE) if (APPLE) diff --git a/src/core/mpris_common.cpp b/src/core/artloader.cpp similarity index 89% rename from src/core/mpris_common.cpp rename to src/core/artloader.cpp index 1e04159df..7fdd7cbcc 100644 --- a/src/core/mpris_common.cpp +++ b/src/core/artloader.cpp @@ -16,13 +16,11 @@ */ #include "albumcoverloader.h" -#include "mpris_common.h" +#include "artloader.h" #include #include -namespace mpris { - ArtLoader::ArtLoader(QObject* parent) : QObject(parent), temp_file_pattern_(QDir::tempPath() + "/clementine-art-XXXXXX.jpg"), @@ -56,6 +54,7 @@ void ArtLoader::TempArtLoaded(quint64 id, const QImage& image) { QString uri; QString thumbnail_uri; + QImage thumbnail; if (!image.isNull()) { temp_art_.reset(new QTemporaryFile(temp_file_pattern_)); @@ -66,16 +65,13 @@ void ArtLoader::TempArtLoaded(quint64 id, const QImage& image) { // since it's the GUI thread, but the alternative is hard. temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_)); temp_art_thumbnail_->open(); - image.scaledToHeight(120, Qt::SmoothTransformation) - .save(temp_art_thumbnail_->fileName(), "JPEG"); + thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation); + thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG"); uri = "file://" + temp_art_->fileName(); thumbnail_uri = "file://" + temp_art_thumbnail_->fileName(); } - emit ArtLoaded(last_song_, uri); - emit ThumbnailLoaded(last_song_, thumbnail_uri); + emit ArtLoaded(last_song_, uri, image); + emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail); } - - -} // namespace mpris diff --git a/src/core/artloader.h b/src/core/artloader.h new file mode 100644 index 000000000..c6b8f71e8 --- /dev/null +++ b/src/core/artloader.h @@ -0,0 +1,62 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef ARTLOADER_H +#define ARTLOADER_H + +#include "backgroundthread.h" +#include "song.h" + +#include + +#include + +class AlbumCoverLoader; + +class QImage; +class QTemporaryFile; + +class ArtLoader : public QObject { + Q_OBJECT + +public: + ArtLoader(QObject* parent = 0); + ~ArtLoader(); + +public slots: + void LoadArt(const Song& song); + +signals: + void ArtLoaded(const Song& song, const QString& uri, const QImage& image); + void ThumbnailLoaded(const Song& song, const QString& uri, const QImage& image); + +private slots: + void Initialised(); + void TempArtLoaded(quint64 id, const QImage& image); + +private: + QString temp_file_pattern_; + + boost::scoped_ptr temp_art_; + boost::scoped_ptr temp_art_thumbnail_; + BackgroundThread* cover_loader_; + quint64 id_; + + Song last_song_; +}; + +#endif // ARTLOADER_H diff --git a/src/core/mpris.cpp b/src/core/mpris.cpp index 915f3569c..984df55ea 100644 --- a/src/core/mpris.cpp +++ b/src/core/mpris.cpp @@ -18,6 +18,7 @@ #include "mpris.h" #include "mpris1.h" #include "mpris2.h" +#include "core/artloader.h" namespace mpris { diff --git a/src/core/mpris.h b/src/core/mpris.h index 153a2e606..45fc571d2 100644 --- a/src/core/mpris.h +++ b/src/core/mpris.h @@ -20,11 +20,11 @@ #include +class ArtLoader; class Player; namespace mpris { -class ArtLoader; class Mpris1; class Mpris2; diff --git a/src/core/mpris1.cpp b/src/core/mpris1.cpp index 3e8dfe497..22cbb69e7 100644 --- a/src/core/mpris1.cpp +++ b/src/core/mpris1.cpp @@ -15,6 +15,7 @@ along with Clementine. If not, see . */ +#include "artloader.h" #include "mpris1.h" #include "mpris_common.h" diff --git a/src/core/mpris1.h b/src/core/mpris1.h index cc6d07f06..0d6727b22 100644 --- a/src/core/mpris1.h +++ b/src/core/mpris1.h @@ -24,6 +24,7 @@ #include #include +class ArtLoader; class PlayerInterface; class Playlist; @@ -54,7 +55,6 @@ Q_DECLARE_METATYPE(Version); QDBusArgument& operator <<(QDBusArgument& arg, const Version& version); const QDBusArgument& operator >>(const QDBusArgument& arg, Version& version); - namespace mpris { enum DBusCaps { @@ -68,8 +68,6 @@ enum DBusCaps { CAN_HAS_TRACKLIST = 1 << 6, }; - -class ArtLoader; class Mpris1Root; class Mpris1Player; class Mpris1TrackList; diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp index 2c8ba3cbd..ff22dfc36 100644 --- a/src/core/mpris2.cpp +++ b/src/core/mpris2.cpp @@ -19,6 +19,7 @@ #include "mpris_common.h" #include "mpris1.h" #include "mpris2.h" +#include "core/artloader.h" #include "core/mpris2_player.h" #include "core/mpris2_root.h" #include "core/mpris2_tracklist.h" @@ -56,7 +57,7 @@ Mpris2::Mpris2(PlayerInterface* player, ArtLoader* art_loader, QDBusConnection::sessionBus().registerService(kServiceName); QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this); - connect(art_loader, SIGNAL(ArtLoaded(Song,QString)), SLOT(ArtLoaded(Song,QString))); + connect(art_loader, SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString))); connect(player->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); connect(player, SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged())); diff --git a/src/core/mpris2.h b/src/core/mpris2.h index d4a2bf77d..445db8b2d 100644 --- a/src/core/mpris2.h +++ b/src/core/mpris2.h @@ -26,6 +26,7 @@ #include +class ArtLoader; class MainWindow; class PlayerInterface; @@ -35,7 +36,6 @@ Q_DECLARE_METATYPE(TrackMetadata) namespace mpris { -class ArtLoader; class Mpris1; class Mpris2 : public QObject { diff --git a/src/core/mpris_common.h b/src/core/mpris_common.h index e37d642a8..361aaff65 100644 --- a/src/core/mpris_common.h +++ b/src/core/mpris_common.h @@ -18,53 +18,13 @@ #ifndef MPRIS_COMMON_H #define MPRIS_COMMON_H -#include "backgroundthread.h" -#include "song.h" - #include #include #include #include -#include - -class AlbumCoverLoader; - -class QImage; -class QTemporaryFile; - namespace mpris { -class ArtLoader : public QObject { - Q_OBJECT - -public: - ArtLoader(QObject* parent = 0); - ~ArtLoader(); - -public slots: - void LoadArt(const Song& song); - -signals: - void ArtLoaded(const Song& song, const QString& uri); - void ThumbnailLoaded(const Song& song, const QString& uri); - -private slots: - void Initialised(); - void TempArtLoaded(quint64 id, const QImage& image); - -private: - QString temp_file_pattern_; - - boost::scoped_ptr temp_art_; - boost::scoped_ptr temp_art_thumbnail_; - BackgroundThread* cover_loader_; - quint64 id_; - - Song last_song_; -}; - - inline void AddMetadata(const QString& key, const QString& metadata, QVariantMap* map) { if (!metadata.isEmpty()) (*map)[key] = metadata; } diff --git a/src/main.cpp b/src/main.cpp index daf1be173..9446ab4ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,7 @@ #endif // Q_OS_WIN32 #include "config.h" +#include "core/artloader.h" #include "core/commandlineoptions.h" #include "core/database.h" #include "core/encoding.h" @@ -80,7 +81,6 @@ using boost::scoped_ptr; #endif #ifdef HAVE_DBUS - #include "core/mpris_common.h" #include "core/mpris.h" #include "core/mpris2.h" #include "dbus/metatypes.h" @@ -92,11 +92,6 @@ using boost::scoped_ptr; const QDBusArgument& operator>> (const QDBusArgument& arg, QImage& image); #endif -#ifdef HAVE_REMOTE -#include "remote/xmpp.h" -#include "remote/zeroconf.h" -#endif - class GstEnginePipeline; // Load sqlite plugin on windows and mac. @@ -316,25 +311,21 @@ int main(int argc, char *argv[]) { scoped_ptr tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); OSD osd(tray_icon.get()); + ArtLoader art_loader; + #ifdef HAVE_DBUS qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType >(); - mpris::ArtLoader art_loader; mpris::Mpris mpris(&player, &art_loader); QObject::connect(&playlists, SIGNAL(CurrentSongChanged(Song)), &art_loader, SLOT(LoadArt(Song))); - QObject::connect(&art_loader, SIGNAL(ThumbnailLoaded(Song, QString)), + QObject::connect(&art_loader, SIGNAL(ThumbnailLoaded(Song, QString, QImage)), &osd, SLOT(CoverArtPathReady(Song, QString))); #endif -#ifdef HAVE_REMOTE - XMPP xmpp; - xmpp.Connect(); -#endif - // Window MainWindow w( database.get(), @@ -343,7 +334,8 @@ int main(int argc, char *argv[]) { &radio_model, &player, tray_icon.get(), - &osd); + &osd, + &art_loader); #ifdef HAVE_DBUS QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise())); #endif diff --git a/src/remote/avahi.cpp b/src/remote/avahi.cpp deleted file mode 100644 index 3f0538c1d..000000000 --- a/src/remote/avahi.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "avahi.h" - -#include -#include - -#include - -#include "dbus/avahientrygroup.h" -#include "dbus/avahiserver.h" - -Avahi::Avahi() - : server_(NULL), - entry_group_(NULL) -{ -} - -void Avahi::Publish(const QString& domain, - const QString& type, - const QString& name, - quint16 port) { - if (server_) { - // Already published - return; - } - - domain_ = domain; - type_ = type; - name_ = name; - port_ = port; - - server_ = new OrgFreedesktopAvahiServerInterface( - "org.freedesktop.Avahi", - "/", - QDBusConnection::systemBus(), - this); - - QDBusPendingReply reply = server_->EntryGroupNew(); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply); - connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - SLOT(EntryGroupNewFinished(QDBusPendingCallWatcher*))); -} - -void Avahi::EntryGroupNewFinished(QDBusPendingCallWatcher* call) { - call->deleteLater(); - QDBusPendingReply reply = *call; - - if (reply.isError()) { - qWarning() << "Failed to create new Avahi entry group:" << call->error().message(); - return; - } - - entry_group_ = new OrgFreedesktopAvahiEntryGroupInterface( - "org.freedesktop.Avahi", - reply.value().path(), - QDBusConnection::systemBus(), - this); - - QDBusPendingReply<> add_reply = entry_group_->AddService( - -1, // Interface (Unspecified, ie. all interfaces) - -1, // Protocol (Unspecified, ie. IPv4 & IPv6) - 0, // Flags - name_, // Service name - type_, // Service type - domain_, // Domain, ie. local - QString::null, // Hostname (Avahi fills it if it's null) - port_, // Port - QList()); // TXT record - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(add_reply); - connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - SLOT(AddServiceFinished(QDBusPendingCallWatcher*))); -} - -void Avahi::AddServiceFinished(QDBusPendingCallWatcher* call) { - call->deleteLater(); - - if (call->isError()) { - qWarning() << "Failed to add Avahi service:" << call->error().message(); - return; - } - - QDBusPendingReply<> commit_reply = entry_group_->Commit(); - QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(commit_reply); - connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - SLOT(CommitFinished(QDBusPendingCallWatcher*))); -} - -void Avahi::CommitFinished(QDBusPendingCallWatcher* call) { - call->deleteLater(); - - if (call->isError()) { - qWarning() << "Failed to commit Avahi changes:" << call->error().message(); - } else { - qDebug() << "Remote interface published on Avahi"; - } -} diff --git a/src/remote/avahi.h b/src/remote/avahi.h deleted file mode 100644 index 1496a98c2..000000000 --- a/src/remote/avahi.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef AVAHI_H -#define AVAHI_H - -#include "zeroconf.h" - -#include - -class OrgFreedesktopAvahiEntryGroupInterface; -class OrgFreedesktopAvahiServerInterface; - -class QDBusPendingCallWatcher; - -class Avahi : public QObject, public Zeroconf { - Q_OBJECT - -public: - Avahi(); - - virtual void Publish(const QString& domain, - const QString& type, - const QString& name, - quint16 port); - -private slots: - void EntryGroupNewFinished(QDBusPendingCallWatcher* call); - void AddServiceFinished(QDBusPendingCallWatcher* call); - void CommitFinished(QDBusPendingCallWatcher* call); - -private: - OrgFreedesktopAvahiServerInterface* server_; - OrgFreedesktopAvahiEntryGroupInterface* entry_group_; - - QString domain_; - QString type_; - QString name_; - quint16 port_; -}; - -#endif diff --git a/src/remote/bonjour.h b/src/remote/bonjour.h deleted file mode 100644 index 41fccac4c..000000000 --- a/src/remote/bonjour.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef BONJOUR_H -#define BONJOUR_H - -#include "zeroconf.h" - -class BonjourWrapper; - -class Bonjour : public Zeroconf { - public: - Bonjour(); - virtual ~Bonjour(); - void Publish(const QString& domain, const QString& type, const QString& name, quint16 port); - - private: - BonjourWrapper* wrapper_; -}; - -#endif diff --git a/src/remote/bonjour.mm b/src/remote/bonjour.mm deleted file mode 100644 index 448f1ce1e..000000000 --- a/src/remote/bonjour.mm +++ /dev/null @@ -1,84 +0,0 @@ -#include "bonjour.h" - -#include - -#import - -@interface NetServicePublicationDelegate : NSObject { - NSMutableArray* services_; -} - -- (void)netServiceWillPublish:(NSNetService*)netService; -- (void)netService:(NSNetService*)netService - didNotPublish:(NSDictionary*)errorDict; -- (void)netServiceDidStop:(NSNetService*)netService; - -@end - -@implementation NetServicePublicationDelegate - -- (id)init { - self = [super init]; - if (self) { - services_ = [[NSMutableArray alloc] init]; - } - return self; -} - -- (void)dealloc { - [services_ release]; - [super dealloc]; -} - -- (void)netServiceWillPublish: (NSNetService*)netService { - [services_ addObject: netService]; - qDebug() << Q_FUNC_INFO - << [[netService name] UTF8String]; -} - -- (void)netService: (NSNetService*)netService didNotPublish: (NSDictionary*)errorDict { - [services_ removeObject: netService]; - qDebug() << Q_FUNC_INFO; - NSLog(@"%@", errorDict); -} - -- (void)netServiceDidStop: (NSNetService*)netService { - [services_ removeObject: netService]; - qDebug() << Q_FUNC_INFO; -} - -@end - -class BonjourWrapper { - public: - BonjourWrapper() { - delegate_ = [[NetServicePublicationDelegate alloc] init]; - } - - NetServicePublicationDelegate* delegate() const { return delegate_; } - - private: - NetServicePublicationDelegate* delegate_; -}; - -#define QSTRING_TO_NSSTRING(x) \ - [[NSString alloc] initWithUTF8String:x.toUtf8().constData()] - -Bonjour::Bonjour() - : wrapper_(new BonjourWrapper) { -} - -Bonjour::~Bonjour() { - delete wrapper_; -} - -void Bonjour::Publish(const QString& domain, const QString& type, const QString& name, quint16 port) { - NSNetService* service = [[NSNetService alloc] initWithDomain: QSTRING_TO_NSSTRING(domain) - type: QSTRING_TO_NSSTRING(type) - name: QSTRING_TO_NSSTRING(name) - port: port]; - if (service) { - [service setDelegate: wrapper_->delegate()]; - [service publish]; - } -} diff --git a/src/remote/remote.cpp b/src/remote/remote.cpp new file mode 100644 index 000000000..3cc631d23 --- /dev/null +++ b/src/remote/remote.cpp @@ -0,0 +1,165 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine 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 Clementine. If not, see . +*/ + +#include "remote.h" +#include "remoteconfig.h" +#include "core/player.h" +#include "engines/enginebase.h" +#include "playlist/playlist.h" +#include "playlist/playlistmanager.h" + +#include +#include + +Remote::Remote(Player* player, QObject* parent) + : QObject(parent), + player_(player), + connection_(new xrme::Connection(this)), + retry_count_(0) +{ + connection_->SetMediaPlayer(this); + connection_->set_verbose(true); + connect(connection_, SIGNAL(Connected()), SLOT(Connected())); + connect(connection_, SIGNAL(Disconnected(QString)), SLOT(Disconnected(QString))); + + connect(player_, SIGNAL(Playing()), SLOT(SetStateChanged())); + connect(player_, SIGNAL(Paused()), SLOT(SetStateChanged())); + connect(player_, SIGNAL(Stopped()), SLOT(SetStateChanged())); + connect(player_, SIGNAL(PlaylistFinished()), SLOT(SetStateChanged())); + connect(player_, SIGNAL(VolumeChanged(int)), SLOT(SetStateChanged())); + connect(player_, SIGNAL(Seeked(qlonglong)), SLOT(SetStateChanged())); + connect(player_->playlists(), SIGNAL(CurrentSongChanged(Song)), SLOT(SetStateChanged())); + + ReloadSettings(); +} + +void Remote::ReloadSettings() { + QSettings s; + s.beginGroup(RemoteConfig::kSettingsGroup); + + QString username = s.value("username").toString(); + QString password = s.value("password").toString(); + QString agent_name = s.value("agent_name", RemoteConfig::DefaultAgentName()).toString(); + + // Have the settings changed? + if (username != connection_->username() || + password != connection_->password() || + agent_name != connection_->agent_name()) { + connection_->set_username(username); + connection_->set_agent_name(agent_name); + connection_->set_password(password); + + if (connection_->is_connected()) { + // We'll reconnect later + connection_->Disconnect(); + } else if (is_configured()) { + connection_->Connect(); + } + } +} + +bool Remote::is_configured() const { + return !connection_->username().isEmpty() && + !connection_->password().isEmpty() && + !connection_->agent_name().isEmpty(); +} + +void Remote::Connected() { + retry_count_ = 0; +} + +void Remote::Disconnected(const QString& error) { + if (retry_count_++ >= kMaxRetries) { + // Show an error and give up if we're above the retry count + if (!error.isEmpty()) { + emit Error("XMPP remote control disconnected: " + error); + } + } else if (is_configured()) { + // Try again + QTimer::singleShot(0, connection_, SLOT(Connect())); + } +} + +void Remote::PlayPause() { + player_->PlayPause(); +} + +void Remote::Stop() { + player_->Stop(); +} + +void Remote::Next() { + player_->Next(); +} + +void Remote::Previous() { + player_->Previous(); +} + +xrme::State Remote::state() const { + const Playlist* active = player_->playlists()->active(); + const Engine::State state = player_->GetState(); + const PlaylistItemPtr current_item = player_->GetCurrentItem(); + + xrme::State ret; + ret.can_go_next = active->next_row() != -1 || + active->current_item_options() & PlaylistItem::ContainsMultipleTracks; + ret.can_go_previous = active->previous_row() != -1; + ret.can_seek = current_item && + current_item->Metadata().filetype() != Song::Type_Stream; + + switch (state) { + case Engine::Playing: ret.playback_state = xrme::State::PlaybackState_Playing; break; + case Engine::Paused: ret.playback_state = xrme::State::PlaybackState_Paused; break; + case Engine::Idle: + case Engine::Empty: ret.playback_state = xrme::State::PlaybackState_Stopped; break; + } + + ret.position_millisec = player_->engine()->position_nanosec() / kNsecPerMsec; + ret.volume = double(player_->GetVolume()) / 100; + + if (current_item) { + const Song m = current_item->Metadata(); + + ret.metadata.title = m.title(); + ret.metadata.artist = m.artist(); + ret.metadata.album = m.album(); + ret.metadata.albumartist = m.albumartist(); + ret.metadata.composer = m.composer(); + ret.metadata.genre = m.genre(); + ret.metadata.track = m.track(); + ret.metadata.disc = m.disc(); + ret.metadata.year = m.year(); + ret.metadata.length_millisec = m.length_nanosec() / kNsecPerMsec; + ret.metadata.rating = m.rating(); + } + + return ret; +} + +QImage Remote::album_art() const { + return last_image_; +} + +void Remote::SetStateChanged() { + StateChanged(); +} + +void Remote::ArtLoaded(const Song&, const QString&, const QImage& image) { + last_image_ = image; + AlbumArtChanged(); +} diff --git a/src/remote/remote.h b/src/remote/remote.h new file mode 100644 index 000000000..7652ae941 --- /dev/null +++ b/src/remote/remote.h @@ -0,0 +1,73 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef REMOTE_H +#define REMOTE_H + +#include "core/song.h" + +#include + +#include +#include + +class ArtLoader; +class Player; + +class Remote : public QObject, + protected xrme::MediaPlayerInterface { + Q_OBJECT + +public: + Remote(Player* player, QObject* parent = 0); + + static const int kMaxRetries = 3; + +public slots: + void ReloadSettings(); + void ArtLoaded(const Song&, const QString&, const QImage& image); + +signals: + void Error(const QString& message); + +protected: + // xrme::MediaPlayerInterface + void PlayPause(); + void Stop(); + void Next(); + void Previous(); + xrme::State state() const; + QImage album_art() const; + +private slots: + void Connected(); + void Disconnected(const QString& error); + + void SetStateChanged(); + +private: + bool is_configured() const; + +private: + Player* player_; + xrme::Connection* connection_; + + QImage last_image_; + int retry_count_; +}; + +#endif // REMOTE_H diff --git a/src/remote/remoteconfig.cpp b/src/remote/remoteconfig.cpp index b67f4d88c..cb871055e 100644 --- a/src/remote/remoteconfig.cpp +++ b/src/remote/remoteconfig.cpp @@ -19,8 +19,8 @@ #include "ui_remoteconfig.h" #include "ui/iconloader.h" -#include "keychain.h" - +#include +#include #include #include #include @@ -46,6 +46,11 @@ RemoteConfig::RemoteConfig(QWidget *parent) resize(sizeHint()); } +QString RemoteConfig::DefaultAgentName() { + return QString("%1 on %2").arg(QCoreApplication::applicationName(), + QHostInfo::localHostName()); +} + RemoteConfig::~RemoteConfig() { delete ui_; } @@ -95,11 +100,13 @@ void RemoteConfig::AuthenticationComplete(bool success) { ui_->busy->hide(); waiting_for_auth_ = false; - if (success) { - validated_password_ = ui_->password->text(); - ui_->password->clear(); - } else { + if (!success) { QMessageBox::warning(this, tr("Authentication failed"), tr("Your Google credentials were incorrect")); + } else { + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("password", ui_->password->text()); + ui_->password->clear(); } emit ValidationComplete(success); @@ -108,24 +115,25 @@ void RemoteConfig::AuthenticationComplete(bool success) { void RemoteConfig::Load() { QSettings s; s.beginGroup(kSettingsGroup); - QVariant username = s.value("username"); - if (username.isValid()) { - ui_->username->setText(username.toString()); - } + + ui_->username->setText(s.value("username").toString()); + ui_->agent_name->setText(s.value("agent_name", DefaultAgentName()).toString()); } void RemoteConfig::Save() { QSettings s; s.beginGroup(kSettingsGroup); - const QString& username = ui_->username->text(); - s.setValue("username", username); - Keychain* keychain = Keychain::getDefault(); - keychain->setPassword(username, validated_password_); - validated_password_.clear(); + + s.setValue("username", ui_->username->text()); + s.setValue("agent_name", ui_->agent_name->text()); } void RemoteConfig::SignOut() { ui_->username->clear(); ui_->password->clear(); ui_->sign_out->setEnabled(false); + + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("password", QString()); } diff --git a/src/remote/remoteconfig.h b/src/remote/remoteconfig.h index 7f9a12093..5a5e1b405 100644 --- a/src/remote/remoteconfig.h +++ b/src/remote/remoteconfig.h @@ -31,6 +31,8 @@ class RemoteConfig : public QWidget { RemoteConfig(QWidget* parent = 0); ~RemoteConfig(); + static QString DefaultAgentName(); + bool NeedsValidation() const; static const char* kSettingsGroup; @@ -54,8 +56,6 @@ class RemoteConfig : public QWidget { Ui_RemoteConfig* ui_; bool waiting_for_auth_; NetworkAccessManager* network_; - - QString validated_password_; }; #endif // REMOTECONFIG_H diff --git a/src/remote/remoteconfig.ui b/src/remote/remoteconfig.ui index 713b42b86..b5f36a15e 100644 --- a/src/remote/remoteconfig.ui +++ b/src/remote/remoteconfig.ui @@ -6,20 +6,27 @@ 0 0 - 773 - 555 + 507 + 437 - + + + + + Clementine can be controlled remotely by an Android phone. To enable this feature log in with the same Google account that is configured on your phone. + + + true + + + Account details - - QFormLayout::FieldsStayAtSizeHint - @@ -27,20 +34,6 @@ - - - - Google password - - - - - - - QLineEdit::Password - - - @@ -65,6 +58,49 @@ + + + + Google password + + + + + + + QLineEdit::Password + + + + + + + + + + Settings + + + + + + Player name + + + + + + + + + + If you use the remote on more than one computer, this name will help you choose which one to connect to on your phone. + + + true + + + diff --git a/src/remote/xmpp.cpp b/src/remote/xmpp.cpp deleted file mode 100644 index bc10b149a..000000000 --- a/src/remote/xmpp.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "xmpp.h" - -#include - -#include -#include - -#include "keychain.h" -#include "remoteconfig.h" - -using gloox::Client; -using gloox::ConnectionTCPClient; -using gloox::JID; -using gloox::MessageSession; - -XMPP::XMPP() { - -} - -XMPP::~XMPP() { - -} - -void XMPP::Connect() { - QSettings s; - s.beginGroup(RemoteConfig::kSettingsGroup); - QVariant username = s.value("username"); - if (username.isValid()) { - Keychain* keychain = Keychain::getDefault(); - QString password = keychain->getPassword(username.toString()); - Connect(username.toString() + "/clementine", password); - } else { - qWarning() << "No username or password set."; - } -} - -void XMPP::Connect(const QString& jid, const QString& password) { - // TODO: Generate <256 char resource. - JID j(jid.toUtf8().constData()); - - client_.reset(new Client(j, password.toUtf8().constData())); - ConnectionTCPClient* connection = new ConnectionTCPClient( - client_.get(), client_->logInstance(), "talk.google.com"); - client_->setConnectionImpl(connection); - - client_->registerConnectionListener(this); - client_->registerMessageHandler(this); - client_->logInstance().registerLogHandler(gloox::LogLevelDebug, gloox::LogAreaAll, this); - client_->setPresence(gloox::PresenceAvailable, -128); - client_->connect(false); - - notifier_.reset(new QSocketNotifier(connection->socket(), QSocketNotifier::Read)); - connect(notifier_.get(), SIGNAL(activated(int)), SLOT(Receive())); -} - -void XMPP::handleMessage(gloox::Stanza* stanza, MessageSession* session) { - qDebug() << Q_FUNC_INFO; - qDebug() << stanza->xml().c_str(); - gloox::Stanza* reply = gloox::Stanza::createMessageStanza( - stanza->from(), "Hello world!"); - client_->send(reply); -} - -void XMPP::onConnect() { - qDebug() << "Connected with resource:" << client_->resource().c_str() - << client_->jid().full().c_str(); - client_->login(); -} - -void XMPP::onDisconnect(gloox::ConnectionError e) { - qDebug() << "Disconnected:" << e; - notifier_->setEnabled(false); -} - -bool XMPP::onTLSConnect(const gloox::CertInfo& info) { - return true; -} - -void XMPP::Receive() { - client_->recv(); -} - -void XMPP::handleLog(gloox::LogLevel level, gloox::LogArea area, - const std::string& message) { - QString prefix = "---"; - if (area == gloox::LogAreaXmlIncoming) { - prefix = "<<<"; - } else if (area == gloox::LogAreaXmlOutgoing) { - prefix = ">>>"; - } - - qDebug() << "XMPP" << prefix.toAscii().constData() << message.c_str(); -} diff --git a/src/remote/xmpp.h b/src/remote/xmpp.h deleted file mode 100644 index dd087d66e..000000000 --- a/src/remote/xmpp.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef XMPP_H -#define XMPP_H - -#include -#include -#include -#include - -#include - -#include -#include - -class XMPP : public QObject, - public gloox::ConnectionListener, - public gloox::MessageHandler, - public gloox::LogHandler { - Q_OBJECT - public: - XMPP(); - virtual ~XMPP(); - - void Connect(); - void Connect(const QString& jid, const QString& password); - - private slots: - void Receive(); - - private: - // gloox::MessageHandler - virtual void handleMessage(gloox::Stanza* stanza, - gloox::MessageSession* session = 0); - - // gloox::ConnectionListener - virtual void onConnect(); - virtual void onDisconnect(gloox::ConnectionError e); - virtual bool onTLSConnect(const gloox::CertInfo& info); - - // gloox::LogHandler - virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, - const std::string& message); - - boost::scoped_ptr client_; - boost::scoped_ptr notifier_; -}; - -#endif // XMPP_H diff --git a/src/remote/zeroconf.cpp b/src/remote/zeroconf.cpp deleted file mode 100644 index d83c1475d..000000000 --- a/src/remote/zeroconf.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "zeroconf.h" - -#include "config.h" - -#ifdef Q_OS_DARWIN -#include "bonjour.h" -#endif - -#ifdef HAVE_DBUS -#include "avahi.h" -#endif - -Zeroconf* Zeroconf::instance_ = NULL; - -Zeroconf* Zeroconf::GetZeroconf() { - if (!instance_) { - #if defined(Q_OS_DARWIN) - instance_ = new Bonjour(); - #elif defined(HAVE_DBUS) - instance_ = new Avahi(); - #endif - } - - return instance_; -} diff --git a/src/remote/zeroconf.h b/src/remote/zeroconf.h deleted file mode 100644 index 94e565565..000000000 --- a/src/remote/zeroconf.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef ZEROCONF_H -#define ZEROCONF_H - -#include - -class Zeroconf { -public: - virtual ~Zeroconf() {} - - virtual void Publish(const QString& domain, - const QString& type, - const QString& name, - quint16 port) = 0; - - static Zeroconf* GetZeroconf(); - -private: - static Zeroconf* instance_; -}; - -#endif diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 513580877..453f628ee 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -17,6 +17,7 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include "core/artloader.h" #include "core/backgroundstreams.h" #include "core/commandlineoptions.h" #include "core/database.h" @@ -103,6 +104,10 @@ # include "visualisations/visualisationcontainer.h" #endif +#ifdef HAVE_REMOTE +# include "remote/remote.h" +#endif + #include #include #include @@ -148,6 +153,7 @@ MainWindow::MainWindow( Player* player, SystemTrayIcon* tray_icon, OSD* osd, + ArtLoader* art_loader, QWidget* parent) : QMainWindow(parent), ui_(new Ui_MainWindow), @@ -162,6 +168,7 @@ MainWindow::MainWindow( player_(player), library_(NULL), global_shortcuts_(new GlobalShortcuts(this)), + remote_(NULL), devices_(NULL), library_view_(new LibraryViewContainer(this)), file_view_(new FileView(this)), @@ -574,6 +581,14 @@ MainWindow::MainWindow( connect(global_shortcuts_, SIGNAL(RateCurrentSong(int)), playlists_, SLOT(RateCurrentSong(int))); + // XMPP Remote control +#ifdef HAVE_REMOTE + remote_ = new Remote(player_, this); + connect(remote_, SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString))); + connect(art_loader, SIGNAL(ArtLoaded(Song,QString,QImage)), + remote_, SLOT(ArtLoaded(Song,QString,QImage))); +#endif + // Fancy tabs connect(ui_->tabs, SIGNAL(ModeChanged(FancyTabWidget::Mode)), SLOT(SaveGeometry())); connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(SaveGeometry())); @@ -733,6 +748,9 @@ void MainWindow::ReloadAllSettings() { #ifdef ENABLE_WIIMOTEDEV wiimotedev_shortcuts_->ReloadSettings(); #endif +#ifdef HAVE_REMOTE + remote_->ReloadSettings(); +#endif } void MainWindow::MediaStopped() { diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index 079c84857..53f92e713 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -34,6 +34,7 @@ class About; class AddStreamDialog; class ArtistInfoView; +class ArtLoader; class BackgroundStreams; class CommandlineOptions; class Database; @@ -58,6 +59,7 @@ class QueueManager; class RadioItem; class RadioModel; class RadioViewContainer; +class Remote; class ScriptDialog; class ScriptManager; class Song; @@ -88,6 +90,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { Player* player, SystemTrayIcon* tray_icon, OSD* osd, + ArtLoader* art_loader, QWidget *parent = 0); ~MainWindow(); @@ -255,6 +258,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { Player* player_; Library* library_; GlobalShortcuts* global_shortcuts_; + Remote* remote_; DeviceManager* devices_;